Bash išvestis ir kanalo išvesties būsena

Noriu atlikti ilgą komandą Bash'e ir užregistruoti savo išėjimo būseną, ir „ tee“ išėjimą.

Taigi, tai darau:

 command | tee out.txt ST=$? 

Problema ta, kad ST kintamasis užfiksuoja tee išėjimo būseną, o ne komandą. Kaip tai išspręsti?

Atkreipkite dėmesį, kad komanda yra ilgas ir iš naujo nukreipiamas išvestis į failą, kad vėliau jį peržiūrėtumėte.

343
03 авг. flybywire rinkinys 03 rug . 2009-08-03 14:31 '09, 14:31, 2009-08-03 14:31
@ 15 atsakymų

Yra vidinis $PIPESTATUS kintamasis, vadinamas $PIPESTATUS ; jos masyvą, kuriame yra kiekvienos komandos paskutinio įgulos vado vamzdyno išėjimo būsena.

 <command> | tee out.txt ; test ${PIPESTATUS[0]} -eq 0 

Arba kita alternatyva, kuri taip pat veikia su kitais korpusais (pvz., Zsh), yra įtraukti vamzdyną:

 set -o pipefail ... 

Pirmasis variantas neveikia su zsh dėl šiek tiek kitokios sintaksės.

431
03 авг. cODAR atsakymas 03 rug . 2009-08-03 14:40 '09 ne 14:40 2009-08-03 14:40

naudinga bash set -o pipefail naudinga

border=0

„pipefail“: dujotiekio grįžtamoji vertė yra paskutinės komandos išėjimo iš nulinės būsenos būsena arba nulis, jei komanda nepaliko nulinės būsenos

127
06 нояб. Atsakymą pateikė Felipe Alvarez 06 lapkritis 2013-11-06 07:15 '13, 07:15 am 2013-11-06 07:15

Nepretenzingas sprendimas: prijunkite juos prie pavadinto vamzdžio (mkfifo). Tada komanda gali būti vykdoma antrą kartą.

  mkfifo pipe tee out.txt < pipe  command > pipe echo $? 
99
03 авг. atsakymas duotas EFraim 03 rug . 2009-08-03 14:34 '09 ne 14:34 2009-08-03 14:34

Yra masyvas, kuris suteikia jums kiekvienos vamzdžio komandos išėjimo būseną.

 $ cat x| sed 's///' cat: x: No such file or directory $ echo $? 0 $ cat x| sed 's///' cat: x: No such file or directory $ echo ${PIPESTATUS[*]} 1 0 $ touch x $ cat x| sed 's' sed: 1: "s": substitute pattern can not be delimited by newline or backslash $ echo ${PIPESTATUS[*]} 0 1 
33
03 авг. Stefano Borini atsakymas 03 rugpjūtis 2009-08-03 14:35 '09, 14:35, 2009-08-03 14:35

Šis sprendimas veikia be bash specifinių funkcijų ar laikinų failų. Premija: galiausiai išėjimo būsena iš tikrųjų yra išėjimo būsena, o ne tam tikra failo eilutė.

Padėtis:

 someprog | filter 

norite gauti išėjimo statusą iš someprog ir išeiti iš filter .

Čia yra mano sprendimas:

 ((((someprog; echo $? > | filter > 3> | (read xs; exit $xs)) 4> echo $? 

Išsamiai paaiškinkite, kaip tai veikia ir kai kurios išlygos, žr. „ Mano atsakymas į tą patį klausimą“ adresu unix.stackexchange.com .

21
13 мая '13 в 23:45 2013-05-13 23:45 atsakymas į „ lesmaną “ gegužės 13 d. 13:45 2013-05-13 23:45

Sujungus PIPESTATUS[0] ir exit rezultatą, galite tiesiogiai pasiekti pradinės komandos grąžinimo vertę:

command | tee ; ( exit ${PIPESTATUS[0]} )

Štai pavyzdys:

 # the "false" shell built-in command returns 1 false | tee ; ( exit ${PIPESTATUS[0]} ) echo "return value: $?" 

suteiks jums:

return value: 1

17
18 авг. atsakymas pateiktas 18 d. 2013-08-18 06:56 '13, 6:56, 2013-08-18 06:56

Taigi norėjau prisidėti, pavyzdžiui, „lesmana“, bet manau, kad gali būti šiek tiek paprastesnis ir šiek tiek pelningesnis švarus „Bourne-shell“ sprendimas:

 # You want to pipe command1 through command2: exec 4> exitstatus=`{ { command1; printf $? 1> } | command2 1> } 3> # $exitstatus now has command1 exit status. 

Manau, kad tai geriausia paaiškinti iš išorės - komanda1 vykdys ir spausdins įprastą išvestį į stdout (failo deskriptorius 1), o tada, kai tai bus padaryta, printf vykdys ir išvesties išvesties kodą icommand1 į savo stdout, tačiau šis stdout nukreipiamas bylos apraše 3.

Nors komanda1 veikia, jo stdout siunčiamas komandai2 komandoje2 (printf išvestis niekada nesukelia į komandą2, nes mes siunčiame ją failo deskriptoriui 3 vietoj 1, kurį skaito). Tada mes perkeliame komandos2 išvestį į failo deskriptorių 4, todėl jis taip pat lieka už failo deskriptoriaus 1 - nes norime, kad failų deskriptorius 1 būtų išleistas šiek tiek vėliau, nes mes išvestume printf produkciją į failo deskriptorių 3 atgal į failo deskriptorių 1. - nes tai, kad bus užfiksuotas komandų submeniu (backticks) ir kas bus įtrauktas į kintamąjį.

Paskutinis magijos bitas yra tas, kad mes sukūrėme pirmąjį exec 4>> kaip atskirą komandą - ji atveria failo deskriptorių 4 kaip išorinio komandų failo stdout kopiją. Komandų pakeitimas bus užfiksuotas viskas, kas parašyta standartiniu lygiu iš jo viduje esančių komandų, bet kadangi komandų 2 išvestis bus valdoma su aprašu Nr. 4 prieš komandų pakeitimą, komandų pakeitimas nėra užfiksuotas, tačiau, kai tik jis gauna „išėjimą“ iš komandų pakeitimo, tai vis dar lieka bendrame scenarijaus failų apraše.

( exec 4>> turėtų būti atskira komanda, nes daugelis normalių kriauklių nepatinka, kai bandote rašyti į failų deskriptorių, esantį komandų pakeitimo, kuris yra atidarytas išorinėje komandoje, kuri naudoja pakaitalą. Taigi tai yra paprasčiausias nešiojamas būdas tai padaryti. )

Jūs galite tai pažvelgti į mažiau techninį ir žaismingesnį būdą, tarsi komandų išėjimai šokinėjo vienas į kitą: komanda1 vamzdis į komandą2, tada printf išėjimas įsijungia į komandą 2, kad komandai2 nebūtų sugautas, o tada 2 komandų viršijimai ir minios nuo komandų pakeitimo, lygiai taip, kaip printf nusileidžia laiku, kad gautų užfiksavimą pakeičiant, kad jis patektų į kintamąjį, o komandos2 išvestis tęsia įdomų būdą, kuris parašytas į standartinę išvestį, kaip ir įprastame vamzdyje.

Be to, kaip suprasti $? vis dar turės antros komandos grąžinimo kodą kanale, nes kintamieji priskyrimai, komandų pakeitimai ir sudėtinės komandos yra efektyviai permatomos komandų grąžinimo kodui jų viduje, todėl turėtų būti paskirstyta komandų 2 grąžinimo būsena - tai nereikia apibrėžti papildomos funkcijos, todėl manau, kad tai gali būti šiek tiek geresnė nei pasiūlyta lesmanos.

Remiantis išlyga, paminėta „lesmana“, galima, kad komanda 1 tam tikru momentu baigsis naudojant 3 ar 4 failų deskriptorius, kad užtikrintumėte didesnį patikimumą:

 exec 4> exitstatus=`{ { command1 3> printf $? 1> } 4> | command2 1> } 3> exec 4>> 

Atkreipkite dėmesį, kad mano pavyzdyje naudoju sudėtines komandas, tačiau subshells (naudojant ( ) vietoj { } taip pat veiks, bet gali būti mažiau veiksmingas.)

Komandos paveda failų deskriptorius iš procesų, kurie juos vykdo, todėl visa antroji eilutė paveldi keturias bylos deskriptorius, o sudėtinė komanda, po kurios 3>> , paveldi failo deskriptorių tris. Taigi, 4>> garantuoja, kad vidinė ryšio komanda nepaveldės keturių failų deskriptoriaus, o 3>> nebus paveldėti trys failų aprašai, todėl komanda1 gauna švaresnę, standartiškesnę aplinką. Taip pat galite perkelti vidinį 4>> šalia 3>> , bet manau, kodėl gi ne kiek įmanoma apriboti jos taikymo sritį.

Nesu tikras, kaip dažnai daiktai naudoja tiesioginius trijų ir keturių failų deskriptorius - manau, kad dauguma laiko programų naudoja sistemos skambučius, kurie grąžina tuo metu nenaudojamus failų aprašus, tačiau kartais kodas rašo 3 failo deskriptoriui (galiu įsivaizduoti programa, tikrinanti failo deskriptorių, kad pamatytų, ar ji yra atvira, ir naudokite ją, jei ji yra, arba elgiasi kitaip, jei ne). Todėl pastaroji greičiausiai geriausiai atsižvelgiama ir naudojama bendrai naudojamiems atvejams.

8
05 июня '15 в 7:33 2015-06-05 07:33 atsakymas pateikiamas mtraceur birželis 05 '15 , 7:33 2015-06-05 07:33

„Ubuntu“ ir „Debian“ galite apt-get install moreutils . Jame yra įrankis, vadinamas mispipe , kuris grąžina pirmojo kanalo išėjimo būseną.

4
13 дек. Atsakymą pateikė Bryan Larsen gruodžio 13 d. 2013-12-13 21:33 '13, 9:33 pm 2013-12-13 21:33
 (command | tee out.txt; exit ${PIPESTATUS[0]}) 

Skirtingai nuo atsakymo į @cODAR, grąžinamas pirmosios pirmosios komandos išėjimo kodas, ne tik 0 sėkmei, o 127 - gedimui. Tačiau, kaip pažymėjo @Cororan, galite tiesiog paskambinti ${PIPESTATUS[0]} . Tačiau svarbu, kad viskas būtų pateikta skliausteliuose.

3
25 сент. atsakymas duotas jakob-r 25 sep . 2017-09-25 11:42 '17, 11:42 2017-09-25 11:42

PIPESTATUS [@] turi būti nukopijuotas į masyvą iškart po to, kai grąžinama vamzdžio komanda. Bet kokie PIPESTATUS [@] įrašai ištrins turinį. Jei planuojate patikrinti visų dujotiekio komandų būseną, nukopijuokite jį į kitą masyvą. „$?“ tai yra ta pati vertė, kaip ir paskutinysis elementas „$ {PIPESTATUS [@]}“, ir skaitymas, atrodo, sunaikina „$ {PIPESTATUS [@]}“, bet aš ne visai patvirtinau.

 declare -a PSA cmd1 | cmd2 | cmd3 PSA=( "${PIPESTATUS[@]}" ) 

Tai neveiks, jei vamzdis yra apvalkalas. Norėdami išspręsti šią problemą,
žr .

3
02 нояб. atsakymas duotas maxdev137 02 Lap. 2013-11-02 06:01 '13, 6:01 am 2013-11-02 06:01

Už bash galite padaryti:

 bash -o pipefail -c "command1 | tee output" 

Tai naudinga, pavyzdžiui, ninja scenarijuose, kai tikimasi, kad apvalkalas bus /bin/sh .

2
17 февр. Atsakymą pateikė Anthony Scemama 17 vasaris. 2016-02-17 21:53 '16 at 9:53 pm 2016-02-17 21:53

Paprasčiausias būdas tai padaryti įprastu būdu yra naudoti procesų pakeitimą vietoj dujotiekio. Yra keletas skirtumų, bet jie tikriausiai nėra labai svarbūs jūsų naudojimo atvejui:

  • Pradėjus vamzdyną, „bash“ laukia, kol bus užbaigti visi procesai.
  • Ctrl-C siuntimas į bash jį nulemia visus vamzdynų procesus, o ne tik pagrindinius.
  • pipefail parametras ir PIPESTATUS kintamasis nėra susiję su proceso pakeitimu.
  • Gal daugiau

Pakeitus procesą, „bash“ tik pradeda procesą ir pamiršo apie jį, jis net nėra rodomas jobs .

Neatsižvelgiant į skirtumus, consumer < <(producer) ir producer | consumer producer | consumer iš esmės yra lygiavertis.

Jei norite apversti, kuris iš jų yra pagrindinis, paprasčiausiai užfiksuokite komandas ir pakeitimo kryptį producer > >(consumer) . Jūsų atveju:

 command > >(tee out.txt) 

Pavyzdys:

 $ { echo "hello world"; false; } > >(tee out.txt) hello world $ echo $? 1 $ cat out.txt hello world $ echo "hello world" > >(tee out.txt) hello world $ echo $? 0 $ cat out.txt hello world 

Kaip sakiau, skirtumas nuo vamzdžio išraiška. Procesas niekada negali nustoti, jei jis nėra jautrus vamzdžio uždarymui. Visų pirma, jis gali toliau rašyti daiktus į jūsų stdout, kuris gali būti painus.

2
15 мая '17 в 9:06 2017-05-15 09:06 atsakymą pateikė „ clacke“ gegužės 15 d., 17 val

Atsakymo @ brian-s-wilson pagrindas; Ši bash pagalbininko funkcija:

 pipestatus() { local S=("${PIPESTATUS[@]}") if test -n "$*" then test "$*" = "${S[*]}" else ! [[ "${S[@]}" =~ [^0\ ] ]] fi } 

naudojamas taip:

1: get_bad_things turėtų būti sėkmingas, tačiau jis neturėtų rodyti rezultatų; bet mes norime matyti rezultatą, kurį jis gamina

 get_bad_things | grep '^' pipeinfo 0 1 || return 

2: visas vamzdynas turi būti sėkmingas

 thing | something -q | thingy pipeinfo || return 
1
15 янв. Atsakymą pateikė Sam Liddicott sausio 15 d 2016-01-15 18:29 '16 at 18:29 pm 2016-01-15 18:29

Išvalykite korpuso tirpalą:

 % rm -f error.flag; echo hello world \ | (cat || echo "First command failed: $?" >> error.flag) \ | (cat || echo "Second command failed: $?" >> error.flag) \ | (cat || echo "Third command failed: $?" >> error.flag) \ ; test -s error.flag  (echo Some command failed: ; cat error.flag) hello world 

O dabar, kai antroji cat pakeičiama false :

 % rm -f error.flag; echo hello world \ | (cat || echo "First command failed: $?" >> error.flag) \ | (false || echo "Second command failed: $?" >> error.flag) \ | (cat || echo "Third command failed: $?" >> error.flag) \ ; test -s error.flag  (echo Some command failed: ; cat error.flag) Some command failed: Second command failed: 1 First command failed: 141 

Atkreipkite dėmesį, kad pirmoji katė nepavyksta, nes ji užsidaro. Nepavykusių komandų tvarka žurnale yra teisinga šiame pavyzdyje, tačiau nesiremkite ja.

Šis metodas leidžia užfiksuoti stdout ir stderr atskiroms komandoms, kad būtų galima ją iškrauti į žurnalo failą, jei įvyko klaida, arba tiesiog ją ištrinti, jei nėra klaidos (pvz., Dd išėjimas).

1
31 марта '15 в 13:08 2015-03-31 13:08 atsakymą pateikė „ Coroos “ kovo 31 d. 15 d., 13:08 2015-03-31 13:08

Kartais gali būti lengviau ir intuityviau naudoti išorinę komandą, o ne įdėti į bash detales. Dujotiekis , iš minimalios scenarijų kalbos, turi antros komandos * grąžinimo kodą, kaip tai yra su vamzdynu, tačiau, skirtingai nuo sh , tai leidžia keisti kanalo kryptį, kad galėtume užfiksuoti gamintojo grąžinimo kodo procesą (visi žemiau komandoje sh , bet su execline nustatymu):

 $ # using the full execline grammar with the execlineb parser: $ execlineb -c 'pipeline { echo "hello world" } tee out.txt' hello world $ cat out.txt hello world $ # for these simple examples, one can forego the parser and just use "" as a separator $ # traditional order $ pipeline echo "hello world" "" tee out.txt hello world $ # "write" order (second command writes rather than reads) $ pipeline -w tee out.txt "" echo "hello world" hello world $ # pipeline execs into the second command, so that the RC we get $ pipeline -w tee out.txt "" false; echo $? 1 $ pipeline -w tee out.txt "" true; echo $? 0 $ # output and exit status $ pipeline -w tee out.txt "" sh -c "echo 'hello world'; exit 42"; echo "RC: $?" hello world RC: 42 $ cat out.txt hello world 

pipeline turi tokius pačius skirtumus, kaip ir vietiniai „bash“ vamzdynai, kaip atsakyme naudojamas „bash“ paprogramis. 43972501 .

* Tiesą sakant, pipeline neveikia, jei įvyksta klaida. Jis atlieka antrą komandą, taigi antroji komanda atlieka grąžinimą.

1
04 мая '16 в 10:32 2016-05-04 10:32 atsakymą pateikė clacke 04 gegužės 16 d. 10:32 2016-05-04 10:32

Kiti klausimai apie „ žymes „ arba „ Klauskite“