Hogyan olvashatok soronként egy bash változóból?

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

  • Ha meg akarja tartani az összes szóközt, akkor használja a while IFS= read …. meg akarja akadályozni az \ értelmezést, akkor használja a read -r
  • I ‘ rögzítettem a fred.bear említett pontjait, valamint megváltoztattam echo printf %s, hogy a szkript még nem szelíd bevitel mellett is működjön.
  • Többsoros változóból történő olvasáshoz inkább a herfest kell választani, mint a printf-ről (lásd: l0b0 ‘ s válasz).
  • @ata Bár én ‘ hallottam ezt ” preferable ” elég gyakran, meg kell jegyezni, hogy a heringeléshez mindig meg kell írni a /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. a printf csővel.
  • A második példában, ha a többsoros változó nem tartalmaz ‘ t új sor után az utolsó elem elveszik. Módosítsa a következőre: printf "%s\n" "$var" | while IFS= read -r line

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 a set +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 a set -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. A IFS 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.

  • A 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. A while read;do ;done <<< "$var" miatt a huroktest nem alhéj. (A Legutóbbi bash-nak lehetősége van egy cmd | 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 a IFS 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, a ProcessText1 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 az ProcessLine 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.

    Vélemény, hozzászólás?

    Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük