Kuinka voin lukea rivi riviltä muuttujasta bashissa?

Minulla on muuttuja, joka sisältää komennon monirivisen ulostulon. Mikä on tehokkain tapa lukea lähtö riviltä riviltä muuttujasta?

Esimerkiksi:

jobs="$(jobs)" if [ "$jobs" ]; then # read lines from $jobs fi 

Vastaa

Voit käyttää while-silmukkaa prosessikorvauksella:

while read -r line do echo "$line" done < <(jobs) 

Optimaalinen tapa lukea monirivinen muuttuja on asettaa tyhjä IFS -muuttuja ja printf muuttuja sisään uudella rivillä:

# 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") 

Huomaa: Kuten shellcheck sc2031 , prosessiaseman käyttö on parempi kuin putki, jotta vältetään [hienovaraisesti] alikuoren luominen.

Huomaa myös, että nimeämällä muuttuja jobs se voi aiheuttaa sekaannusta, koska se on myös yhteisen shell-komennon nimi.

Kommentit

  • Jos haluat säilyttää kaikki tyhjät tilasi, käytä while IFS= read …. haluat estää \ tulkinnan, käytä sitten read -r
  • I ’ olen korjannut mainitut kohdat fred.bear sekä muuttanut echo arvoksi printf %s, jotta komentosarja toimisi myös muulla kuin kesyttämättömällä syötteellä.
  • Jos haluat lukea monirivimuuttujasta, on parempi kuin herf, ei putkia printf: stä (katso l0b0 ’ s vastaus).
  • @ata Vaikka olen ’ kuullut tämän ” preferable ” riittävän usein, on huomattava, että paimentaminen vaatii aina hakemiston /tmp olevan kirjoitettavissa, koska se perustuu pystyy luomaan väliaikaisen työtiedoston. Jos joskus löydät itsesi rajoitetusta järjestelmästä, jossa /tmp on vain luku -tilassa (etkä sinä voi muuttaa sitä), olet iloinen mahdollisuudesta käyttää vaihtoehtoista ratkaisua, esim. g. printf -putkella.
  • Toisessa esimerkissä, jos monirivimuuttuja ’ ei sisällä uuden rivin perässä menetät viimeisen elementin. Vaihda se muotoon: printf "%s\n" "$var" | while IFS= read -r line

Vastaa

komentorivin lähtö riviltä ( selitys ):

jobs | while IFS= read -r line; do process "$line" done 

Jos sinulla on jo muuttujan tiedot:

printf %s "$foo" | … 

printf %s "$foo" on melkein identtinen echo "$foo", mutta tulostaa $foo kirjaimellisesti, kun taas echo "$foo" saattaa tulkita vaihtoehdon $foo echo-komentoon, jos se alkaa merkinnällä -, ja saattaa laajentaa takaisinkelausjaksoja osassa $foo joissakin kuorissa.

Huomaa, että joissakin kuorissa (tuhka, bash, pdksh, mutta ei ksh tai zsh) putkilinjan oikea puoli kulkee erillisessä prosessissa, joten kaikki silmukassa asettamasi muuttujat menetetään. Esimerkiksi seuraava rivilaskentaohjelma skannaa 0 näihin kuoreihin:

n=0 printf %s "$foo" | while IFS= read -r line; do n=$(($n + 1)) done echo $n 

Kiertotapa on laittaa loppu komentosarjasta (tai ainakin sen osa joka tarvitsee arvon $n silmukasta) komentolistassa:

n=0 printf %s "$foo" | { while IFS= read -r line; do n=$(($n + 1)) done echo $n } 

Jos ei-tyhjille riveille toimiminen on tarpeeksi hyvää eikä syötteitä ole paljon, voit käyttää sanojen jakamista:

IFS=" " set -f for line in $(jobs); do # process line done set +f unset IFS 

Selitys: asetus IFS yhdeksi uudeksi riviksi sanan jakaminen tapahtuu vain uudella rivillä (toisin kuin mikä tahansa välilyönti, joka on oletusasetuksessa). set -f kytkee pois huijaamisen (ts. jokerimerkkilaajennuksen), joka muuten tapahtuisi komennon korvaamisen seurauksena $(jobs) tai muuttuvalla korvaamisella $foo. for -silmukka vaikuttaa kaikkiin $(jobs) -kappaleisiin, jotka ovat kaikki komentotuloksen tyhjät rivit. Palauta lopuksi huojuvat ja IFS -asetukset oletusarvoja vastaaviksi arvoiksi.

Kommentit

  • Minulla on ollut vaikeuksia asettaa IFS ja poistaa IFS. Mielestäni oikea tapa on tallentaa IFS: n vanha arvo ja asettaa IFS takaisin siihen vanhaan arvoon. En ’ ole bash-asiantuntija, mutta kokemukseni mukaan tämä vie sinut takaisin alkuperäiseen bahavioriin.
  • @BjornRoche: käytä funktion sisällä local IFS=something. Se ei vaikuta ’ t vaikuttamaan globaalin arvon arvoon. IIRC, unset IFS ei palaa ’ ei palauta oletusta (ja varmasti ei ’ ei toimi, jos se ei ollut etukäteen oletusarvoisesti ’ t).
  • Halusin käyttää set tapaa viimeisessä esimerkissä esitetyt tiedot ovat oikein.Koodinpätkä olettaa, että set +f oli aktiivinen alussa, ja palauttaa sen vuoksi kyseisen asetuksen loppuun. Tämä oletus voi kuitenkin olla väärä. Entä jos set -f oli aktiivinen alussa?
  • @Binarus palautan vain oletusarvoja vastaavat asetukset. Jos haluat palauttaa alkuperäiset asetukset, sinun on tehtävä enemmän työtä. Tallenna set -f -kohteelle alkuperäinen $-. Jos kyseessä on IFS, se ’ on ärsyttävän hölmö, jos sinulla ’ ei ole local ja haluat tukea tapausta, jonka asetuksia ei ole asetettu; jos haluat palauttaa sen, suosittelen pakottamaan muuttujan, joka IFS pysyy asetettuna.
  • local todellakin paras ratkaisu, koska local - tekee komentoasetuksista paikalliset ja local IFS tekee IFS paikallinen. Valitettavasti local on voimassa vain toimintojen sisällä, mikä tekee koodin uudelleenjärjestelystä tarpeen. Ehdotuksesi ottaa käyttöön käytäntö, jonka IFS asetetaan aina, kuulostaa myös hyvin kohtuulliselta ja ratkaisee suurimman osan ongelmasta. Kiitos!

Vastaa

Ongelma: jos käytät loop-komentoa, se toimii alikuoressa ja kaikki muuttujat ovat menetetty. Ratkaisu: käytä silmukalle

# 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= 

Kommentit

  • KIITOS PALJON !! Kaikki yllä olevat ratkaisut epäonnistuivat.
  • putkitus while read -silmukkaan bashissa tarkoittaa, että silmukka on alikuoressa, joten muuttujia ei ole ’ t globaali. while read;do ;done <<< "$var" tekee silmukan rungosta ei alikuoren. (Viimeaikaisella bashilla on mahdollisuus sijoittaa cmd | while -silmukan runko ei alikuoreen, kuten ksh: llä on aina ollut.)
  • Katso myös tämä aiheeseen liittyvä viesti .
  • Samankaltaisissa tilanteissa minusta oli yllättävän vaikeaa kohdella IFS oikein. Tässä ratkaisussa on myös ongelma: Entä jos IFS ei ole asetettu ollenkaan alussa (ts. On määrittelemätön)? Se määritetään joka tapauksessa kyseisen koodinpätkän jälkeen; tämä ei ’ näytä olevan oikea.

Vastaa

jobs="$(jobs)" while IFS= read -r do echo "$REPLY" done <<< "$jobs" 

Viitteet:

Kommentit

  • -r on myös hyvä idea; Se estää \` interpretation... (it is in your links, but its probably worth mentioning, just to round out your IFS = `(mikä on välttämätöntä tyhjätilan häviämisen estämiseksi)
  • Vain tämä ratkaisu toimi minulle. Kiitos brah.
  • Ei ’ t tämä ratkaisu kärsi samasta ongelmasta, joka on mainittu kommenteissa osoitteeseen @dogbane ’ Vastaus? Entä jos muuttujan viimeistä riviä ei lopeta uuden rivin merkki?
  • Tämä vastaus tarjoaa puhtaimman tavan syöttää muuttujan sisältö while read rakenna.

Vastaa

Käytä uusimmissa bash-versioissa mapfile tai readarray, jotta voit lukea komennon tehokkaasti taulukoiksi

$ readarray test < <(ls -ltrR) $ echo ${#test[@]} 6305 

Vastuuvapauslauseke: kauhea esimerkki, mutta voit tulla kovasti mukaan paremmalla komennolla kuin ls itse.

Kommentit

  • Se ’ on mukava tapa, mutta pentueet / var / tmp väliaikaisilla tiedostoilla järjestelmässäni. +1 joka tapauksessa
  • @eugene: se on ’ hauskaa. Millä järjestelmällä (distro / OS) se on?
  • Se ’ s FreeBSD 8. Kuinka toistaa: laita readarray funktiossa ja kutsu funktiota muutaman kerran.
  • Hieno, @sehe. +1

vastaus

Muissa vastauksissa on annettu yleiset mallit ongelman ratkaisemiseksi.

Haluaisin kuitenkin lisätä lähestymistapani, vaikka en ole varma kuinka tehokas se on. Mutta se (ainakin minulle) hyvin ymmärrettävä, ei muuta alkuperäistä muuttujaa (kaikki ratkaisut, joissa käytetään read on oltava kyseisessä muuttujassa perään uusi viiva ja siksi lisättävä se, mikä muuttaa muuttujaa), ei luo alikuoria (kuten kaikki putkipohjaiset ratkaisut tekevät), ei käytä tässä -merkkijonot (joilla on omat ongelmansa), eikä käytä prosessin korvaamista (ei mitään sitä vastaan, mutta joskus vähän vaikea ymmärtää).

Itse en ymmärrä miksi 69c7f378ea ”>

” integroituja RE: itä käytetään niin harvoin.Ehkä ne eivät ole kannettavia, mutta koska OP on käyttänyt tagiabash, se ei pysäytä minua 🙂

#!/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" 

Tulos:

root@cerberus:~/scripts# ./test4 a1 b1 c1 a2 b2 c2a3 b3 c3root@cerberus:~/scripts# 

Muutama huomautus:

Käyttäytyminen riippuu mitä käytät. Olen käyttänyt yllä olevassa esimerkissä ProcessText1.

Huomaa, että

  • ProcessText1 pitää uuden rivin merkit rivien lopussa
  • ProcessText1 käsittelee muuttujan viimeisen rivin (joka sisältää tekstin) c3) vaikka kyseinen rivi ei sisällä uuden rivin viimeistä merkkiä. Puuttuvan uuden rivin puuttuessa komentokehote komentosarjan suorittamisen jälkeen liitetään viimeiseen muuttujan rivi ilman, että sitä erotetaan lähdöstä.
  • ProcessText1 pitää muuttujan viimeisen uuden rivin ja muuttujan lopun välistä osaa rivinä , vaikka se olisi tyhjä; tietysti kyseisellä rivillä, onko se tyhjä vai ei, ei ole uutta riviä. Toisin sanoen, vaikka muuttujan viimeinen merkki on uusi rivi, ProcessText1 käsittelee viimeisen uuden rivin ja muuttujan lopun välistä tyhjää osaa (tyhjä merkkijono) tyhjä) rivi ja välittää sen linjan käsittelyyn. Voit estää tämän käyttäytymisen helposti kääkemällä toisen puhelun numeroon ProcessLine sopivaan check-if-tyhjään ehtoon; Minusta on kuitenkin loogisempaa jättää se sellaisenaan.

ProcessText1 täytyy soittaa ProcessLine kahdessa paikassa, mikä saattaa olla epämukavaa, jos haluat sijoittaa sinne koodilohkon, joka käsittelee suoraan linjan sen sijaan, että kutsuisit linjaa käsittelevän funktion; joudut toistamaan virhealtista koodia.

Sen sijaan ProcessText3 käsittelee linjaa tai kutsuu vastaavaa toimintoa vain yhteen paikkaan, jolloin korvaa toimintokutsu koodilla estää ei-brainerin. Tämä maksaa kahden while ehdon yhden sijasta. Toteutuserojen lisäksi ProcessText3 käyttäytyy täsmälleen samalla tavalla kuin ProcessText1, paitsi että se ei ota huomioon viimeisen uuden rivin välistä osaa muuttuja ja muuttujan loppu rivinä, jos kyseinen osa on tyhjä. Toisin sanoen ProcessText3 ei siirry rivinkäsittelyyn muuttujan viimeisen uuden rivin merkin jälkeen, jos uusi rivin merkki on muuttujan viimeinen merkki.

ProcessText2 toimii kuten ProcessText1, paitsi että riveillä on oltava perässä uusi rivimerkki. Toisin sanoen muuttujan viimeisen uuden rivin merkin ja muuttujan lopun välistä osaa ei pidetä viivana ja se heitetään hiljaa pois. Näin ollen, jos muuttuja ei sisällä mitään uutta rivimerkkiä, rivin käsittelyä ei tapahdu lainkaan.

Pidän tästä lähestymistavasta enemmän kuin muut yllä esitetyt ratkaisut, mutta luultavasti olen jättänyt jotain väliin (en ole kovin kokenut bash ohjelmointi ja et ole kovin kiinnostunut muista kuoreista).

Vastaa

Voit käyttää < < < lukeaksesi yksinkertaisesti muuttujasta, joka sisältää uuden rivin -erotetut tiedot:

while read -r line do echo "A line of input: $line" done <<<"$lines" 

kommentit

  • Tervetuloa Unixiin & Linux! Tämä pääosin kopioi vastauksen neljä vuotta sitten. Älä lähetä vastausta, ellei sinulla ole jotain uutta.

Vastaa

Sähköpostiosoitettasi ei julkaista. Pakolliset kentät on merkitty *