Van egy változóm, amely egy parancs többsoros kimenetét tartalmazza. Mi a leghatékonyabb módja a kimeneti soronkénti olvasásának a változóból?
Például:
jobs="$(jobs)" if [ "$jobs" ]; then # read lines from $jobs fi
Válasz
Használhat egy darab ciklust folyamathelyettesítéssel:
while read -r line do echo "$line" done < <(jobs)
Optimális módszer a egy többsoros változó beolvasása az üres IFS
változó és printf
változó beillesztése egy új sorral:
# Printf "%s\n" "$var" is necessary because printf "%s" "$var" on a # variable that doesn"t end with a newline then the while loop will # completely miss the last line of the variable. while IFS= read -r line do echo "$line" done < <(printf "%s\n" "$var")
Megjegyzés: Az shellcheck sc2031 szerint a folyamat alállomás használata előnyösebb, mint a cső, hogy elkerüljék [finoman] alhéj létrehozása.
Ezenkívül vegye figyelembe, hogy a jobs
változó megnevezésével zavart okozhat, mivel ez egy közös shell parancs neve is.
Megjegyzések
Válasz
A parancssor kimenete soronként ( magyarázat ):
jobs | while IFS= read -r line; do process "$line" done
Ha rendelkezik adatok már egy változóban:
printf %s "$foo" | …
printf %s "$foo"
majdnem megegyezik a echo "$foo"
, de a $foo
szót szó szerint kinyomtatja, míg az echo "$foo"
esetleg értelmezheti az $foo
lehetőséget az echo parancsra, ha -
-vel kezdődik, és egyes héjakban kibővítheti a visszavágó szekvenciákat a $foo
-ben.
Vegye figyelembe, hogy egyes héjakban (ash, bash, pdksh, de nem ksh vagy zsh) a csővezeték jobb oldala külön folyamatban fut, így a ciklusban beállított minden változó elvész. Például a következő sorszámláló szkript 0-t nyomtat ezekbe a héjakba:
n=0 printf %s "$foo" | while IFS= read -r line; do n=$(($n + 1)) done echo $n
Egy megoldás a szkript fennmaradó részének (vagy legalább annak amihez a ciklus $n
értéke szükséges) egy parancslistában:
n=0 printf %s "$foo" | { while IFS= read -r line; do n=$(($n + 1)) done echo $n }
Ha a nem üres sorokra való cselekvés elég jó, és a bemenet nem hatalmas, használhatja a szófelosztást:
IFS=" " set -f for line in $(jobs); do # process line done set +f unset IFS
Magyarázat: egyetlen új vonallá teszi a szavak felosztását csak új vonalaknál (szemben az alapértelmezett beállításban szereplő bármely szóköz karakterrel). A set -f
kikapcsolja a globbingot (azaz a helyettesítő karakter kiterjesztését), ami egyébként a $(jobs)
parancscsere vagy egy változó $foo
. A for
hurok a $(jobs)
összes darabjára hat, amelyek a parancs kimenetének összes nem üres sora. Végül állítsa vissza a globbing és a IFS
beállításokat olyan értékekre, amelyek megegyeznek az alapértelmezettekkel.
Megjegyzések
- Problémáim voltak az IFS beállításával és az IFS kikapcsolásával. Úgy gondolom, hogy a helyes az IFS régi értékének tárolása és az IFS visszaállítása erre a régi értékre. Én ‘ nem vagyok bash szakértő, de tapasztalatom szerint ez visszavezet az eredeti bahaviorhoz.
- @BjornRoche: egy függvény belsejében használja a
local IFS=something
. ‘ nem fogja befolyásolni a globális hatókör értékét. IIRC,unset IFS
nem ‘ nem tér vissza az alapértelmezett értékre (és természetesen nem ‘ akkor nem működik, ha előtte nem volt ‘ t az alapértelmezett). - Kíváncsi vagyok, vajon a
set
lehetőséget az utolsó példában látható helyes.A kódrészlet feltételezi, hogy aset +f
az elején aktív volt, ezért a végén visszaállítja ezt a beállítást. Ez a feltételezés azonban téves lehet. Mi lenne, ha aset -f
aktív volt az elején? - @Binarus Csak az alapértelmezetteknek megfelelő beállításokat állítom vissza. Valóban, ha vissza akarja állítani az eredeti beállításokat, akkor több munkát kell végeznie. A
set -f
esetén mentse az eredeti$-
fájlt. AIFS
esetében ez ‘ bosszantóan fideszes, ha nincs ‘ = “6d857e61ef”>
és támogatni szeretné a törlés nélküli esetet; ha mégis vissza akarja állítani, javasoljuk az invariáns végrehajtását, amelyIFS
állítva marad.
local
használatával valóban a legjobb megoldás, mert a local -
lokálissá teszi a shell opciókat, a local IFS
pedig IFS
helyi. Sajnos a local
csak a függvényeken belül érvényes, ami szükségessé teszi a kód átalakítását. A IFS
mindig beállított irányelv bevezetésének javaslata szintén nagyon ésszerűnek hangzik, és megoldja a probléma legnagyobb részét. Köszönöm! Válasz
Probléma: ha a loop-ot használja, akkor az alhéjban fog futni, és az összes változó elveszett. Megoldás: használja a ciklushoz
# change delimiter (IFS) to new line. IFS_BAK=$IFS IFS=$"\n" for line in $variableWithSeveralLines; do echo "$line" # return IFS back if you need to split new line by spaces: IFS=$IFS_BAK IFS_BAK= lineConvertedToArraySplittedBySpaces=( $line ) echo "{lineConvertedToArraySplittedBySpaces[0]}" # return IFS back to newline for "for" loop IFS_BAK=$IFS IFS=$"\n" done # return delimiter to previous value IFS=$IFS_BAK IFS_BAK=
Megjegyzések
- HOGYAN KÖSZÖNÖM !! A fenti megoldások mindegyike kudarcot vallott számomra.
- a bash
while read
hurokba történő piping azt jelenti, hogy míg a loop egy alhéjban van, tehát a változók ‘ t globális. Awhile read;do ;done <<< "$var"
miatt a huroktest nem alhéj. (A Legutóbbi bash-nak lehetősége van egycmd | while
hurok testét nem alhéjba tenni, mint a ksh-nek mindig volt.) - Lásd még: ez a kapcsolódó bejegyzés .
- Hasonló helyzetekben meglepően nehéznek találtam a
IFS
helyes kezelését. Ennek a megoldásnak van egy problémája is: Mi van, ha aIFS
nincs egyáltalán beállítva az elején (vagyis nincs meghatározva)? A kódrészlet után minden esetben meg lesz határozva; úgy tűnik, ez nem ‘ tűnik helyesnek.
Válasz
jobs="$(jobs)" while IFS= read -r do echo "$REPLY" done <<< "$jobs"
Hivatkozások:
Megjegyzések
-
-r
is jó ötlet; Megakadályozza az\` interpretation... (it is in your links, but its probably worth mentioning, just to round out your
IFS = `alkalmazást (ami elengedhetetlen a szóköz elvesztésének megakadályozásához) - Csak ez a megoldás működött nálam. Köszönöm brah.
- Nem ‘ t ez a megoldás ugyanabban a problémában szenved, amelyet a @dogbane ‘ s válasz? Mi van, ha a változó utolsó sorát nem egy újsoros karakter zárja le?
- Ez a válasz a legtisztább módot szolgáltatja a változó tartalmának a
while read
szerkesztés.
Válasz
A legújabb bash verziókban használja a mapfile
vagy readarray
a parancs kimenetének tömbökhöz való hatékony olvasása
$ readarray test < <(ls -ltrR) $ echo ${#test[@]} 6305
Jogi nyilatkozat: borzalmas példa, de nagyon jöhet jobb parancssal használható, mint az önmaga.
Hozzászólások
- Ez ‘ jó módszer, de alom / var / tmp ideiglenes fájlokkal a rendszeremen. +1 egyébként
- @eugene: ez ‘ vicces. Milyen rendszer (disztribúció / OS) van rajta?
- Ez ‘ s FreeBSD 8. Hogyan kell szaporítani: put
readarray
egy függvényben, és hívja meg néhányszor a függvényt. - Szép, @sehe. +1
Válasz
A probléma megoldására szolgáló közös mintákat a többi válasz megadta.
Mindazonáltal szeretném hozzáadni a megközelítésemet, bár nem vagyok biztos benne, mennyire hatékony. De (legalábbis számomra) meglehetősen érthető, nem változtatja meg az eredeti változót (minden olyan megoldás, amely read
a kérdéses változónak záró új vonallal kell rendelkeznie, ezért hozzá kell adnia, ami megváltoztatja a változót, nem hoz létre alhéjakat (amiket az összes csőalapú megoldás tesz), nem használja itt -húrok (amelyeknek saját problémáik vannak), és nem használ folyamathelyettesítést (semmi ellene, de néha kicsit nehezen érthető).
Valójában nem értem, miért ” integrált RE-ket ilyen ritkán használják.Talán nem hordozhatóak, de mivel az OP a bash
címkét használta, ez nem fog megállítani 🙂
#!/bin/bash function ProcessLine() { printf "%s" "$1" } function ProcessText1() { local Text=$1 local Pattern=$"^([^\n]*\n)(.*)$" while [[ "$Text" =~ $Pattern ]]; do ProcessLine "${BASH_REMATCH[1]}" Text="${BASH_REMATCH[2]}" done ProcessLine "$Text" } function ProcessText2() { local Text=$1 local Pattern=$"^([^\n]*\n)(.*)$" while [[ "$Text" =~ $Pattern ]]; do ProcessLine "${BASH_REMATCH[1]}" Text="${BASH_REMATCH[2]}" done } function ProcessText3() { local Text=$1 local Pattern=$"^([^\n]*\n?)(.*)$" while [[ ("$Text" != "") && ("$Text" =~ $Pattern) ]]; do ProcessLine "${BASH_REMATCH[1]}" Text="${BASH_REMATCH[2]}" done } MyVar1=$"a1\nb1\nc1\n" MyVar2=$"a2\n\nb2\nc2" MyVar3=$"a3\nb3\nc3" ProcessText1 "$MyVar1" ProcessText1 "$MyVar2" ProcessText1 "$MyVar3"
Kimenet:
root@cerberus:~/scripts# ./test4 a1 b1 c1 a2 b2 c2a3 b3 c3root@cerberus:~/scripts#
Néhány megjegyzés:
A viselkedés attól függ, hogy a amit használ. A fenti példában a ProcessText1
-t használtam.
Ne feledje, hogy
-
ProcessText1
új sor karaktereket tart a sorok végén -
ProcessText1
feldolgozza a változó utolsó sorát (amely tartalmazza a szöveget)c3
) bár ez a sor nem tartalmaz záró újsor karaktert. A hiányzó új sor miatt a parancssor végrehajtása utáni parancssor hozzáadódik az utolsóhoz a változó sora, anélkül, hogy elválasztanák a kimenettől. -
ProcessText1
mindig a változó utolsó új sora és a változó vége közötti részt tekinti egyenesnek , még ha üres is; természetesen ennek a sornak, függetlenül attól, hogy üres-e vagy sem, nincs záró újsor karakter. Vagyis, még akkor is, ha a változó utolsó karaktere új sor, aProcessText1
az utolsó új sor és a változó vége közötti üres részt (null string) egy (még üres) sort, és átadja a vonal feldolgozásának. Könnyedén megakadályozhatja ezt a viselkedést, ha a második hívást azProcessLine
címre egy megfelelő check-if-empty feltételbe csomagolja; szerintem azonban logikusabb úgy hagyni, ahogy van.
ProcessText1
meg kell hívni a ProcessLine
két helyen, ami kényelmetlen lehet, ha egy olyan blokkot szeretne elhelyezni, amely közvetlenül a vonalat dolgozza fel, ahelyett, hogy a vonalat feldolgozó függvényt hívna meg; meg kell ismételni a hibára hajlamos kódot.
Ezzel szemben a ProcessText3
a sort feldolgozza, vagy csak egy helyen hívja meg a megfelelő függvényt, kicserélve a függvényhívás egy kóddal blokkol egy nem gondolkodót. Ez egy helyett két while
feltétel árával jár. A megvalósítási különbségektől eltekintve a ProcessText3
pontosan ugyanúgy viselkedik, mint a ProcessText1
, kivéve, hogy nem veszi figyelembe a a változó és a változó vége sorként, ha az a rész üres. Vagyis ProcessText3
nem megy a sor feldolgozásába a változó utolsó újsoros karaktere után, ha ez az újsoros karakter a változó utolsó karaktere.
ProcessText2
úgy működik, mint a ProcessText1
, azzal a különbséggel, hogy a soroknak záró újsor karakterrel kell rendelkezniük. Vagyis a változó utolsó újsoros karaktere és a változó vége közötti részt nem sornak tekintjük vonalnak, és csendben eldobjuk. Következésképpen, ha a változó nem tartalmaz új sor karaktert, akkor egyáltalán nem történik sorfeldolgozás.
Ez a megközelítés jobban tetszik, mint a többi fent bemutatott megoldás, de valószínűleg valamit elmulasztottam (nem nagyon tapasztaltam a bash
programozás, és nem érdekli őket nagyon más kagyló).
Válasz
A < < < segítségével egyszerűen leolvashatja az új sort tartalmazó változóból elválasztott adatok:
while read -r line do echo "A line of input: $line" done <<<"$lines"
Megjegyzések
- Üdvözöljük a Unixban & Linux! Ez lényegében megismétli a négy évvel ezelőtti választ. Kérjük, ne küldjön választ, hacsak nincs valami új hozzászólása.
while IFS= read
…. meg akarja akadályozni az \ értelmezést, akkor használja aread -r
echo
printf %s
, hogy a szkript még nem szelíd bevitel mellett is működjön./tmp
könyvtárat, mivel az a képes ideiglenes munkafájl létrehozására. Ha valaha olyan korlátozott rendszerben találja magát, ahol a/tmp
csak olvasható (és Ön nem változtatható), akkor örülni fog egy alternatív megoldás használatának, pl. g. aprintf
csővel.printf "%s\n" "$var" | while IFS= read -r line