Bu bash'da bir hata mı? Bir borudan çağrılırsa "return" işlevi sonlandırılmaz


16

Son zamanlarda bash ile garip sorunlar yaşıyorum. Senaryomu basitleştirmeye çalışırken, bu küçük kod parçasını buldum:

$ o(){ echo | while read -r; do return 0; done; echo $?;}; o
0
$ o(){ echo | while read -r; do return 1; done; echo $?;}; o
1

returnyazdırmadan işlevden çıkmış $?olmalı, değil mi? Peki, o zaman tek başına bir borudan dönüp dönemeyeceğimi kontrol ettim:

$ echo | while read -r; do return 1; done
bash: return: can only `return' from a function or sourced script

Aynı şey bir whiledöngü olmadan gerçekleşir :

$ foo(){ : | return 1; echo "This should not be printed.";}
$ foo
This should not be printed.

Burada özlediğim bir şey var mı? Bir Google araması bu konuda hiçbir şey getirmedi! Benim bash versiyonum Debian Wheezy'de 4.2.37 (1) .


Yanıtımda önerdiğim ayarlarda, betiğinizin yapmayı beklediğiniz sezgisel şekilde davranmasına izin veren yanlış bir şey var mı?
jlliagre

@jlliagre Binlerce satırda oldukça karmaşık bir senaryodur. Başka bir şeyi kırma endişesi ile, sadece fonksiyon içinde bir boru çalıştırmaktan kaçınmayı tercih ederim, bu yüzden yerine bir proses ikamesi koydum. Teşekkürler!
Teresa e Junior

whileÜreme için gerekli değilse neden ilk iki örneği kaldırmıyorsunuz ? Bu noktadan oyalanıyor.
Monica ile Hafiflik Yarışları

@LightnessRacesinOrbit Bir whiledöngü, bir boru için çok yaygın bir kullanımdır return. İkinci örnek daha doğrudur, ama kimsenin kullanacağına inanmadığım bir şey ...
Teresa e Junior

1
Maalesef doğru cevabım silindi ... Belirtilmemiş bir şey yaptığınızda gri bir bölgedesiniz. Davranış, kabuğun boruları nasıl yorumladığına bağlıdır ve ksh sh kaynaklarından türetilmiş olsa bile, Bourne Kabuğu ve Korn Kabuğu arasında bu bile farklıdır. Bourne Kabuğunda, while döngüsü bir alt kabuktadır, bu nedenle yankı bash ile görürsünüz, ksh'da while döngüsü ön plan işlemidir ve bu nedenle ksh, örneğinizle echo'yu çağırmaz.
schily

Yanıtlar:


10

İlgili: /programming//a/7804208/4937930

Bir komut dosyasından çıkamayacağınız exitveya returnalt kabuklarda veya alt kabuklardaki bir işlevden geri dönemeyeceğiniz bir hata değildir . Başka bir süreçte yürütülürler ve ana süreci etkilemezler.

Bunun yanı sıra, (muhtemelen) tanımlanmamış spec üzerinde bash'ın belgelenmemiş davranışlarını gördüğünüzü varsayalım. Bir işlevde, returnalt kabuk komutlarının en üst düzeyinde hata bildirilmez ve sadece gibi davranır exit.

IMHO return, ana ifadenin bir işlevde olup olmadığına bağlı olarak tutarsız davranışlar için bir bash hatasıdır .

#!/bin/bash

o() {
    # Runtime error, but no errors are asserted,
    # each $? is set to the return code.
    echo | return 10
    echo $?
    (return 11)
    echo $?

    # Valid, each $? is set to the exit code.
    echo | exit 12
    echo $?
    (exit 13)
    echo $?
}
o

# Runtime errors are asserted, each $? is set to 1.
echo | return 20
echo $?
(return 21)
echo $?

# Valid, each $? is set to the exit code.
echo | exit 22
echo $?
(exit 23)
echo $?

Çıktı:

$ bash script.sh 
10
11
12
13
script.sh: line 20: return: can only `return' from a function or sourced script
1
script.sh: line 22: return: can only `return' from a function or sourced script
1
22
23

Hata ayrıntı düzeyinin eksikliği belgelenmemiş olabilir. Ancak returnbir alt kabuktaki üst düzey bir komut dizisinden çalışmayan ve özellikle alt kabuktan çıkmayan, mevcut belgelerin beni zaten beklettiği şeydir. OP kullanmaya exit 1 || return 1çalıştıkları yeri kullanabilir returnve daha sonra beklenen davranışı elde etmelidir. EDIT: @ herbert'in cevabı return, alt kabuktaki üst seviyenin exit(ancak yalnızca alt kabuktan) çalıştığını gösterir.
dubiousjim

1
@dubiousjim Betiğimi güncelledi. Yani returnbir çalışma zamanı hatası olarak iddia edilmelidir basit altkabuk dizisindeki herhangi durumlarda , ama bir fucntion oluştuğunda aslında değil. Bu sorun gnu.bash.bug'da da tartışılmıştır , ancak sonuç yoktur.
yaegashi

1
While döngüsü alt kabukta mı yoksa ön planda mı olduğu belirtilmediğinden, yanıtınız doğru değil. Gerçek kabuğun uygulanmasına bakılmaksızın, returnifade bir işlevdedir ve bu nedenle yasaldır. Ancak ortaya çıkan davranış belirtilmez.
schily

Boru bileşenlerinin bir alt kabukta olduğu gerçeği bash manuel sayfasında belgelenirken, belgesiz bir davranış olduğunu yazmamalısınız. POSIX izin verilen davranışları belirtirken, davranış büyük olasılıkla tanımlanmamış özelliklere dayanarak yazmamalısınız. Bash, POSIX standardını takip ederken bir işlevde dönüşe izin vererek dışarıda değil bir bash hatası olduğundan şüphelenmemelisiniz.
jlliagre

17

Bu bir hata bashdeğil, belgelenmiş davranışıdır :

Bir boru hattındaki her komut kendi alt kabuğunda yürütülür

returnTalimat geçerli bir işlev tanımı içindeki varlık değil, aynı zamanda bir kabuktaki olmanın, bir sonraki talimat böylece üst kabuğunu etkilemez olduğu echobakılmaksızın yürütülür. Bununla birlikte, POSIX standardı , bir boru hattı oluşturan komutların bir alt kabukta (varsayılan) veya üstte (izin verilen bir uzantı) yürütülmesine izin verdiği için taşınabilir olmayan bir kabuk yapısıdır .

Ek olarak, çok komutlu bir boru hattının her komutu alt kabuk ortamındadır; bununla birlikte, bir boru hattındaki komutların herhangi biri veya tümü geçerli ortamda yürütülebilir. Diğer tüm komutlar geçerli kabuk ortamında yürütülür.

Umarım, bashbirkaç seçenekle beklediğiniz şekilde davranmayı söyleyebilirsiniz :

$ set +m # disable job control
$ shopt -s lastpipe # do not run the last command of a pipeline a subshell 
$ o(){ echo | while read -r; do return 0; done; echo $?;}
$ o
$          <- nothing is printed here

1
İşlevden returnçıkmayacağından bash: return: can only `return' from a function or sourced script, kullanıcıya işlevin geri döndüğüne dair yanlış bir anlam vermek yerine , kabuk yeni yazdırılmışsa daha anlamlı olmaz mıydı ?
Teresa e Junior

2
Belgelerde hiçbir yerde göremiyorum, alt kabuğun içindeki dönüşün geçerli olduğunu söyledi . Bahse girerim ki bu özellik ksh'dan kopyalanmıştır, fonksiyonun dışındaki return ifadesi veya kaynaklı komut dosyası çıkış gibi davranır . Orijinal Bourne kabuğundan emin değilim.
cuonglm

1
@jlliagre: Belki Teresa ne istediğinin terminolojisi hakkında kafası karışmış, ama returnbir subshell'den a çalıştırırsanız bash için bir tanı koymanın neden "zor" olduğunu anlamıyorum . Sonuçta, $BASH_SUBSHELLdeğişkenin kanıtladığı gibi bir alt kabukta olduğunu biliyor . En büyük sorun bunun yanlış pozitiflere yol açabilmesidir; alt kabukların nasıl çalıştığını anlayan bir kullanıcı return, exitbir alt kabuğu sonlandırmak yerine kullanılan komut dosyaları yazmış olabilir . (Ve elbette, bir kişinin değişkenleri ayarlamak veya cdbir alt kabukta bir yapmak isteyebileceği geçerli durumlar vardır .)
Scott

1
@Scott Ben durumu iyi anladığımı düşünüyorum. Bir boru alt kabuk oluşturur ve returngerçek bir işlevin içinde olduğu için başarısız olmak yerine alt kabuktan geri döner. Sorun şudur help return: Causes a function or sourced script to exit with the return value specified by N.Belgeleri okuduktan sonra, herhangi bir kullanıcı belgenin en azından başarısız olmasını veya bir uyarı yazdırmasını bekler, ancak asla böyle davranmaz exit.
Teresa e Junior

1
Bana öyle geliyor ki , bir işlevde return alt kabukta bir işlevden (ana kabuk işleminde) geri dönmeyi bekleyen herkes alt kabukları çok iyi anlamıyor. Tersine, alt kabukları anlayan bir okuyucunun return , bir alt kabukta olduğu gibi, bir alt kabukta da alt kabuğu sona erdirmesini beklemesini beklerimexit .
Scott

6

POSIX belgelerine Başına, kullanarak returnfonksiyonun dışında veya kaynaklı senaryoyu belirtilmemiş ise . Yani, ele almak için kabuğunuza bağlıdır.

SystemV kabuk ise, hata bildirir ksh, returnbenzeri fonksiyonun dışında veya kaynaklı komut uslu exit. Diğer POSIX mermilerinin çoğu ve schily's osh da şöyle davranır:

$ for s in /bin/*sh /opt/schily/bin/osh; do
  printf '<%s>\n' $s
  $s -c '
    o(){ echo | while read l; do return 0; done; echo $?;}; o
  '
done
</bin/bash>
0
</bin/dash>
0
</bin/ksh>
</bin/lksh>
0
</bin/mksh>
0
</bin/pdksh>
0
</bin/posh>
0
</bin/sh>
0
</bin/yash>
0
</bin/zsh>
</opt/schily/bin/osh>
0

kshve zshbu kabuklardaki borunun son kısmı, alt kabuk yerine geçerli kabukta yürütüldüğünden çıktı. Return ifadesi, işlevi çağırılan geçerli kabuk ortamını etkiledi ve hiçbir şey yazdırmadan işlevin hemen geri dönmesine neden oldu.

Etkileşimli oturumda, bashyalnızca hatayı bildirin, ancak kabuğu sonlandırmadı schily's osh, hatayı bildirdi ve kabuğu sonlandırdı:

$ for s in /bin/*sh; do printf '<%s>\n' $s; $s -ci 'return 1; echo 1'; done
</bin/bash>
bash: return: can only `return' from a function or sourced script
1
</bin/dash>
</bin/ksh>
</bin/lksh>
</bin/mksh>
</bin/pdksh>
</bin/posh>
</bin/sh>
</bin/yash>
</bin/zsh>
</opt/schily/bin/osh>
$ cannot return when not in function

( zshEtkileşimli oturum ve çıkış terminali do sona değildir bash, yashve schily's oshbir hata rapor ama kabuk sona yoktu)


1
Burada bir fonksiyonun içindereturn kullanıldığı iddia edilebilir .
jlliagre

1
@jlliagre: Şunu emin değil ne returniçeride kullanılmasıydı alt kabuğa içindeki fonksiyonu dışında kshve zsh.
cuonglm

2
Demek istediğim, bir fonksiyonun içinde olan bir alt kabuğun içinde olmak, mutlaka bu fonksiyonun dışında olmak anlamına gelmez, yani standart durumlardaki boru hattı bileşenlerindeki hiçbir şey, bulundukları fonksiyonun dışında kabul edilmemelidir. Bu, Açık Grup tarafından açıklığa kavuşturulmayı hak eder.
jlliagre

3
Bence hayır. Bu fonksiyonun dışında. İşlevi çağıran kabuk ve dönüşü yapan alt kabuk farklıdır.
cuonglm

Sorunu doğru bir şekilde açıklayan mantığınızı anlıyorum, amacım POSIX standardında açıklanan kabuk dilbilgisine göre, boru hattı, fonksiyonun gövdesi olan bileşik komutunun bir parçası olan bileşik listesinin bir parçası. Hiçbir yerde boru hattı bileşenlerinin işlevin dışında dikkate alınacağı belirtilmemiştir. Tıpkı bir arabadayım ve o araba bir garaja park edilmiş gibi, ben de o garajda olduğumu varsayabilirim ;-)
jlliagre

4

Beklenen davranışı aldığınızı düşünüyorum, bash'da, bir boru hattındaki her komut bir alt kabukta yürütülür. İşlevinizin global bir değişkenini değiştirmeye çalışarak kendinizi rahatlatabilirsiniz:

foo(){ x=42; : | x=3; echo "x==$x";}

Bu arada, geri dönüş çalışıyor ama alt kabuktan geri dönüyor. Tekrar kontrol edebilirsiniz:

foo(){ : | return 1; echo$?; echo "This should not be printed.";}

Aşağıdaki çıktıyı verecektir:

1
This should not be printed.

Dönüş ifadesi alt kabuktan doğru şekilde çıkmış

.


2
Bu nedenle, işlevden çıkmak için kullanın foo(){ : | return 1 || return 2; echo$?; echo "This should not be printed.";}; foo; echo $?ve bir sonucu elde edersiniz 2. Ama netlik için ben yapacak return 1davranışsal deneyi exit 1.
dubiousjim

Bu arada, bir boru hattının tüm üyelerinin (biri hariç tümü) alt kabuklarda idam edildiğine dair bir kanıt var mı?
Incnis Mrsi

@IncnisMrsi: Bkz. Jlliagre'ın yanıtı .
Scott

1

Daha genel cevap, bash ve diğer bazı mermilerin normalde bir boru hattının tüm elemanlarını ayrı işlemlere sokmasıdır. Komut satırı

program 1 | program 2 | program 3

çünkü programlar normalde yine de ayrı süreçlerde çalıştırılır (siz söylemezseniz ). Ama bu bir sürpriz olabilirexec program

komut 1 | komut 2 | komut 3

burada komutların bazıları veya tümü yerleşik komutlardır. Önemsiz örnekler şunları içerir:

$ a=0
$ echo | a=1
$ echo "$a"
0
$ cd /
$ echo | cd /tmp
$ pwd
/

Biraz daha gerçekçi bir örnek

$ t=0
$ ps | while read pid rest_of_line
> do
>     : $((t+=pid))
> done
$ echo "$t"
0

Tüm nerede while... do... donedöngü bir alt süreç konur ve bunun değişiklikleri böylece için tdöngü sona erdikten sonra ana kabuğuna görünmez. Ve tam olarak bunu yapıyorsunuz - bir whiledöngüye borulama yapmak , döngünün bir alt kabuk olarak çalışmasına neden olmak ve daha sonra alt kabuktan geri dönmeye çalışmak.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.