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 IFSnem ‘ 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
setlehetőséget az utolsó példában látható helyes.A kódrészlet feltételezi, hogy aset +faz 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 -faktí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 -fesetén mentse az eredeti$-fájlt. AIFSeseté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 readhurokba 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 | whilehurok 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
IFShelyes kezelését. Ennek a megoldásnak van egy problémája is: Mi van, ha aIFSnincs 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
-
-ris jó ötlet; Megakadályozza az\` interpretation... (it is in your links, but its probably worth mentioning, just to round out yourIFS = `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 readszerkeszté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
readarrayegy 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 -
ProcessText1feldolgozza 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. -
ProcessText1mindig 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, aProcessText1az 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 azProcessLinecí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 -rechoprintf %s, hogy a szkript még nem szelíd bevitel mellett is működjön./tmpkö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/tmpcsak olvasható (és Ön nem változtatható), akkor örülni fog egy alternatív megoldás használatának, pl. g. aprintfcsővel.printf "%s\n" "$var" | while IFS= read -r line