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
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ä josset -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ä onIFS
, se ’ on ärsyttävän hölmö, jos sinulla ’ ei olelocal
ja haluat tukea tapausta, jonka asetuksia ei ole asetettu; jos haluat palauttaa sen, suosittelen pakottamaan muuttujan, jokaIFS
pysyy asetettuna. -
local
todellakin paras ratkaisu, koskalocal -
tekee komentoasetuksista paikalliset jalocal IFS
tekeeIFS
paikallinen. Valitettavastilocal
on voimassa vain toimintojen sisällä, mikä tekee koodin uudelleenjärjestelystä tarpeen. Ehdotuksesi ottaa käyttöön käytäntö, jonkaIFS
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 sijoittaacmd | 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ä josIFS
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 numeroonProcessLine
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.
while IFS= read
…. haluat estää \ tulkinnan, käytä sittenread -r
echo
arvoksiprintf %s
, jotta komentosarja toimisi myös muulla kuin kesyttämättömällä syötteellä./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.printf "%s\n" "$var" | while IFS= read -r line