Bagaimana status pengembalian tugas variabel ditentukan?

10

Saya telah melihat konstruk dalam skrip seperti ini:

if somevar="$(somecommand 2>/dev/null)"; then
...
fi

Apakah ini didokumentasikan di suatu tempat? Bagaimana status pengembalian variabel ditentukan dan bagaimana kaitannya dengan substitusi perintah? (Misalnya, apakah saya akan mendapatkan hasil yang sama if echo "$(somecommand 2>/dev/null)"; then?)

Wildcard
sumber

Jawaban:

12

Ini didokumentasikan (untuk POSIX) di Bagian 2.9.1 Perintah Sederhana dari Spesifikasi Basis Grup Terbuka. Ada dinding teks di sana; Saya mengarahkan perhatian Anda ke paragraf terakhir:

Jika ada nama perintah, eksekusi harus dilanjutkan seperti yang dijelaskan dalam Pencarian dan Eksekusi Perintah . Jika tidak ada nama perintah, tetapi perintah berisi substitusi perintah, perintah harus melengkapi dengan status keluar dari substitusi perintah terakhir yang dilakukan. Jika tidak, perintah harus lengkap dengan status keluar nol.

Jadi, misalnya,

   Command                                         Exit Status
$ FOO=BAR                                   0 (but see also the note from icarus, below)
$ FOO=$(bar)                                Exit status from "bar"
$ FOO=$(bar) baz                            Exit status from "baz"
$ foo $(bar)                                Exit status from "foo"

Ini juga cara bash bekerja. Tetapi lihat juga bagian “tidak begitu sederhana” di bagian akhir.

PHK , dalam pertanyaannya Penugasan seperti perintah dengan status keluar kecuali ketika ada substitusi perintah? , menyarankan

... tampaknya seolah-olah suatu tugas itu sendiri dihitung sebagai perintah ... dengan nilai keluar nol, tetapi yang berlaku sebelum sisi kanan tugas (misalnya, panggilan substitusi perintah ...)

Itu bukan cara yang mengerikan untuk melihatnya. Sebuah skema mentah untuk menentukan status kembalinya perintah sederhana (satu tidak mengandung ;, &, |, &&atau ||) adalah:

  • Pindai garis dari kiri ke kanan hingga Anda mencapai akhir atau kata perintah (biasanya nama program).
  • Jika Anda melihat tugas variabel, status pengembalian untuk baris mungkin saja 0.
  • Jika Anda melihat substitusi perintah - yaitu, $(…)- ambil status keluar dari perintah itu.
  • Jika Anda mencapai perintah yang sebenarnya (bukan di substitusi perintah), ambil status keluar dari perintah itu.
  • Status pengembalian untuk baris adalah nomor terakhir yang Anda temui.
    Pergantian perintah sebagai argumen untuk perintah, misalnya, foo $(bar)tidak masuk hitungan; Anda mendapatkan status keluar dari foo. Mengutip notasi phk , perilaku di sini adalah

    temporary_variable  = EXECUTE( "bar" )
    overall_exit_status = EXECUTE( "foo", temporary_variable )

Tapi ini sedikit penyederhanaan. Status pengembalian keseluruhan dari

A = $ ( cmd 1 ) B = $ ( cmd 2 ) C = $ ( cmd 3 ) D = $ ( cmd 4 ) E = mc 2
adalah status keluar dari . The tugas yang terjadi setelah tugas tidak menetapkan status keluar keseluruhan untuk 0.cmd4E=D=

icarus , dalam jawabannya untuk pertanyaan phk , memunculkan poin penting: variabel dapat diatur sebagai hanya dibaca. Paragraf ketiga hingga terakhir dalam Bagian 2.9.1 dari standar POSIX mengatakan,

Jika salah satu dari penugasan variabel mencoba untuk memberikan nilai ke variabel yang atribut readonly diatur dalam lingkungan shell saat ini (terlepas dari apakah penugasan dibuat di lingkungan itu), kesalahan penugasan variabel akan terjadi. Lihat Konsekuensi Kesalahan Shell untuk konsekuensi dari kesalahan ini.

jadi jika Anda mengatakannya

readonly A
C=Garfield A=Felix T=Tigger

status kembali adalah 1. Tidak peduli jika string Garfield, Felixdan / atau Tigger diganti dengan perintah substitusi (s) - tetapi lihat catatan di bawah.

Bagian 2.8.1 Konsekuensi Kesalahan Shell memiliki banyak teks, dan sebuah tabel, dan diakhiri dengan

Dalam semua kasus yang ditunjukkan pada tabel di mana shell interaktif diperlukan untuk tidak keluar, shell tidak akan melakukan pemrosesan lebih lanjut dari perintah di mana kesalahan terjadi.

Beberapa detail masuk akal; beberapa tidak:

  • The A=tugas kadang-kadang dibatalkan baris perintah, sebagai kalimat terakhir tampaknya menentukan. Dalam contoh di atas, Cdiatur ke Garfield, tetapi Ttidak diatur (dan, tentu saja, tidak juga  A).
  • Demikian pula, mengeksekusi tetapi tidak . Tapi, dalam versi saya bash (termasuk 4.1.x dan 4.3.X), itu tidak mengeksekusi . (Kebetulan, ini lebih lanjut mengimplikasikan interpretasi phk bahwa nilai keluar dari penugasan berlaku sebelum sisi kanan penugasan.)C=$(cmd1) A=$(cmd2) T=$(cmd3)cmd1cmd3
    cmd2

Tapi ini kejutan:

Dalam versi bash saya,

baca saja A
C = sesuatu A = sesuatu T = sesuatu  cmd 0

tidak mengeksekusi. Khususnya,cmd0

C = $ ( cmd 1 ) A = $ ( cmd 2 ) T = $ ( cmd 3 )    cmd 0
mengeksekusi dan , tetapi tidak . (Perhatikan bahwa ini adalah kebalikan dari perilakunya ketika tidak ada perintah.) Dan itu menetapkan (dan juga ) di lingkungan . Saya bertanya-tanya apakah ini bug di bash.cmd1cmd3cmd2TCcmd0


Tidak sesederhana itu:

Paragraf pertama dari jawaban ini mengacu pada "perintah sederhana".  Spesifikasi mengatakan,

"Perintah sederhana" adalah urutan penugasan dan pengalihan variabel opsional, dalam urutan apa pun, secara opsional diikuti oleh kata-kata dan pengalihan, diakhiri oleh operator kontrol.

Ini adalah pernyataan seperti yang ada di blok contoh pertama saya:

$ FOO=BAR
$ FOO=$(bar)
$ FOO=$(bar) baz
$ foo $(bar)

tiga yang pertama termasuk tugas variabel, dan tiga yang terakhir termasuk penggantian perintah.

Tetapi beberapa tugas variabel tidak begitu sederhana.  bash (1) mengatakan,

Pernyataan penugasan juga dapat muncul sebagai argumen untuk alias, declare, typeset, export, readonly, dan localbuiltin perintah ( deklarasi perintah).

Sebab export, spesifikasi POSIX mengatakan,

EXIT STATUS

    0
      Semua operan nama berhasil diekspor.
    > 0
      Setidaknya satu nama tidak dapat diekspor, atau -popsi ditentukan dan terjadi kesalahan.

Dan POSIX tidak mendukung local, tetapi bash (1) mengatakan,

Ini adalah kesalahan untuk digunakan localsaat tidak dalam suatu fungsi. Status pengembalian adalah 0 kecuali localdigunakan di luar fungsi, nama yang tidak valid diberikan, atau nama adalah variabel readonly.

Membaca yang tersirat, kita bisa melihat bahwa perintah deklarasi suka

export FOO=$(bar)

dan

local FOO=$(bar)

lebih seperti

foo $(bar)

sejauh mereka mengabaikan status keluar dari bar dan memberikan status keluar berdasarkan perintah utama ( export, local, atau foo). Jadi kita punya keanehan seperti

   Command                                           Exit Status
$ FOO=$(bar)                                    Exit status from "bar"
                                                  (unless FOO is readonly)
$ export FOO=$(bar)                             0 (unless FOO is readonly,
                                                  or other error from “export”)
$ local FOO=$(bar)                              0 (unless FOO is readonly,
                                                  statement is not in a function,
                                                  or other error from “local”)

yang bisa kita tunjukkan dengan

$ export FRIDAY=$(date -d tomorrow)
$ echo "FRIDAY   = $FRIDAY, status = $?"
FRIDAY   = Fri, May 04, 2018  8:58:30 PM, status = 0
$ export SATURDAY=$(date -d "day after tomorrow")
date: invalid date ‘day after tomorrow’
$ echo "SATURDAY = $SATURDAY, status = $?"
SATURDAY = , status = 0

dan

myfunc() {
    local x=$(echo "Foo"; true);  echo "x = $x -> $?"
    local y=$(echo "Bar"; false); echo "y = $y -> $?"
    echo -n "BUT! "
    local z; z=$(echo "Baz"; false); echo "z = $z -> $?"
}

$ myfunc
x = Foo -> 0
y = Bar -> 0
BUT! z = Baz -> 1

Untungnya ShellCheck menangkap kesalahan dan memunculkan SC2155 , yang menyarankan itu

export foo="$(mycmd)"

harus diubah menjadi

foo=$(mycmd)
export foo

dan

local foo="$(mycmd)"

harus diubah menjadi

local foo
foo=$(mycmd)
G-Man Mengatakan 'Reinstate Monica'
sumber
1
Terima kasih banyak! Untuk poin bonus, apakah Anda tahu bagaimana localhubungan ini? Misalnya local foo=$(bar)?
Wildcard
1
Untuk contoh kedua (hanya FOO=$(bar)) perlu dicatat bahwa secara teoritis status keluar dan penugasan mungkin memainkan peran, lihat unix.stackexchange.com/a/341013/117599
phk
1
@ Kartu Memori: Saya senang Anda menyukainya. Saya baru saja memperbaruinya lagi; sebagian besar versi yang baru saja Anda baca salah. Selama kamu di sini, bagaimana menurutmu? Apakah ini bug dalam bash, yang A=foo cmdberjalan cmdbahkan jika Ahanya baca?
G-Man Mengatakan 'Reinstate Monica'
1
@phk: (1) Teori yang menarik, tapi saya tidak yakin bagaimana itu masuk akal. Lihatlah contoh berikut tepat sebelum judul "kejutan" saya. Jika Adibaca hanya, perintah C=value₁ A=value₂ T=value₃menetapkan Ctetapi tidak T(dan, tentu saja, Atidak diatur) - shell dihentikan memproses baris perintah, mengabaikan T=value₃, karena A=value₂merupakan kesalahan. (2) Terima kasih atas tautannya ke pertanyaan Stack Overflow - Saya sudah memposting komentar tentangnya.
G-Man Mengatakan 'Reinstate Monica'
1
@Wildcard "Untuk poin bonus, apakah Anda tahu bagaimana ikatan lokal ke dalam ini?". Ya ... local=$(false)memiliki nilai keluar 0karena (dari halaman manual): It is an error to use local when not within a function. The return status is 0 unless local is used outside a function, an invalid name is supplied, or name is a readonly variable.. Ini tidak cukup untuk menghadapi jenius di dunia untuk jenius yang dirancang seperti itu.
David Tonhofer
2

Itu didokumentasikan dalam Bash ( LESS=+/'^SIMPLE COMMAND EXPANSION' bash):

Jika ada nama perintah yang tersisa setelah ekspansi .... Jika tidak, perintah akan keluar. ... Jika tidak ada penggantian perintah, perintah keluar dengan status nol.

Dengan kata lain (kata-kata saya):

Jika tidak ada nama perintah yang tersisa setelah ekspansi, dan tidak ada penggantian perintah dieksekusi, baris perintah keluar dengan status nol.

phk
sumber