Jag försöker, utan framgång, att använda ett awk
-kommando inuti ett for
loop.
Jag har en variabel som innehåller en serie strängar som jag vill klippa med awk
för att få data.
Jag vet hur man gör det men vad jag verkligen vill är att klippa data successivt.
Så jag har den här variabeln:
var="data1,data2,data3"
Och här där jag är just nu:
for ((i=1; i<=3; i++)) do echo $(awk -F, "{print $1}" <<< $var) done
Jag försöker ersätta $1
av slingan $i
men utan framgång.
Kommentarer
Svar
Du kan åstadkomma det du är försöker göra genom att använda dubbla citat i awk-skriptet för att injicera skalvariabeln i den. Du vill fortfarande behålla en bokstavlig $
i den, vilket du kan göra genom att undkomma den med backslash :
echo $(awk -F, "{print \$$i}" <<<$var)
Detta utvidgar $i
till 1
, 2
och 3
i var och en av iterationerna, därför kommer awk att se $1
, $2
och $3
vilket gör att den utvidgar vart och ett av fälten.
En annan möjlighet är att injicera skalvariabeln som en awk-variabel med flaggan -v
:
echo $(awk -F, -v i="$i" "{print $i}" <<<$var)
Det tilldelar awk-variabeln i till innehållet i skalvariabeln med samma namn. Variabler i awk använder inte en $
, som används för fält, så $i
räcker för att hänvisa till i -th fält om i är en variabel i awk.
Att tilldela en awk-variabel med -v
är i allmänhet säkrare tillvägagångssätt, särskilt när den kan innehålla godtyckliga sekvenser av tecken, i så fall är det mindre risk att innehållet kommer att köras som awk-kod mot dina avsikter. Men eftersom variabeln i ditt fall innehåller ett enda heltal, är det mindre angeläget.
Ännu ett alternativ är att använda en for
loop i awk själv Se awk-dokumentationen (eller sök på den här webbplatsen) för mer information om hur du gör det.
Kommentarer
- … eller ställ in ingångsposten separator på lämpligt sätt
awk -v RS='[,\n]' 1 <<< "$var"
(eller kanske mer portabeltprintf "$var" | awk -v RS=, 1
) - @steeldriver Det awk-skriptet skriver ut alla tre fälten vid vilket är ok med tanke på att OP gör just det … Jag slutade med att fokusera svaret på att extrahera ett enda fält med antagandet att de var intresserade av att utföra andra kommandon i skalslingan (som kan har varit motivationen för att använda en i första hand …)
- Förstått – att ’ varför jag kommenterade OP för att klargöra vad de verkligen vill göra;)
- Den första är en injektion (och ett säkerhetsproblem), den andra o ne (med
-v
) är inte en injektion. - Tack, ditt svar löste mitt problem! Första gången jag lägger upp en fråga här efter år av att ha läst inlägg. Inte besviken. En sådan fantastisk gemenskap!
Svar
Att använda awk
verkar överdriven under denna omständighet, vad sägs om en tr
och en while-loop:
tr , "\n" <<<"$var" | while read; do echo $REPLY done
Output:
data1 data2 data3
Kommentarer
- Bra.
REPLY
som standardname
argument förread
inbyggt skalkommando. - Observera att detta kräver att skalet använder en standardvariabel för att lägga in data från
read
, vilketbash
råkar göra , men detta är inte standard. Dittread
-kommando kan också ändra informationen om det innehåller snedstreck och kan ta bort flankerande vitt utrymme från värdena. Det skulle också läsa värden med inbäddade nya rader som flera värden. Du behöver dessutom"$REPLY"
för att stoppa skalet från att dela upp värdet och utföra filnamnsexpansion på det.
Svar
awk kan acceptera både j
(som variabel) och $j
(som fältindex):
for i in 1 2 3; do echo "$var" | awk -v j=$i -F , "{print $j}"; done
$i
i exemplet” förvirrad ”awk
vilken som ska användas (skal eller en egen variabel – med företräde) som båda hänvisas till med $
prefix.
not
sh skal som är standard för ”portabelt” skript stöder inte:
(( i=1; i<=3; i++; ))
och <<< $var
konstruktioner
Du kan också överväga att använda seq
kommando i for
loop för finare kontroll vid generering av nummersekvenser, om tillgänglig.
Kommentarer
- Din loop fungerar bara om du råkar ha tre filer som heter ,
2
och3
i den aktuella katalogen. Du använder också variabel expansion som inte är citerad i skalet, vilket kan få oönskade konsekvenser om data innehåller filnamnmönster (som*
).echo
kan dessutom modifiera data om de innehåller snedstreck.
Svar
#!/bin/sh var="data1,data2,data3" unset data while [ "$var" != "$data" ]; do data=${var%%,*} # delete first comma and the bit after it var=${var#*,} # delete bit up to first comma (and the comma) printf "data = "%s"\n" "$data" done
Här använder vi variabla substitutioner för att få varje successivt kommadelat avgränsat datafält från värdet för var
-variabeln. Den första tilldelningen till data
i slingan tar bort allt från $var
efter det första komma. Variabeln var
modifieras sedan så att den första biten upp till första komma tas bort.
Detta fortsätter tills "$var" = "$data"
vilket innebär att ingenting mer kan göras mot strängen.
Detta sätt att göra det skulle göra det möjligt för oss att hantera kommaseparerade datasträngar som innehåller inbäddade nya rader:
var="line1 line2,data2,last bit goes here"
Med ovanstående värden i var
skulle ovanstående skript mata ut
data = "line1 line2" data = "data2" data = "last bit goes here"
Att inte bry sig om inbäddade nya rader; Du väldigt sällan måste slingra på anrop av awk
.
Observera att awk
är helt glad att läsa din sträng som en uppsättning kommaseparerade fält och att den kan slinga över dessa:
printf "%s\n" "$var" | awk -F "," "{ for (i=1; i<=NF; i++) print $i }"
Med var="data1,data2,data3"
, detta skulle skriva ut
data1 data2 data3
En annan skallösning som använder IFS
-variabel för att dela upp $var
-värdet i bitar samtidigt som du använder set -f
för att inaktivera filnamnsexpansion:
set -f oldIFS=$IFS; IFS="," set -- $var IFS=$oldIFS; unset oldIFS set +f for data do printf "data = "%s"\n" "$data" done
echo "${var//,/$'\n'}"
)