Bagaimana saya bisa mendapatkan bash untuk keluar dari kegagalan backtick dengan cara yang mirip dengan pipefail?

55

Jadi saya ingin mengeraskan skrip bash saya di mana saja saya bisa (dan ketika tidak dapat mendelegasikan ke bahasa seperti Python / Ruby) untuk memastikan kesalahan tidak terjadi.

Dalam nada itu saya memiliki strict.sh, yang berisi hal-hal seperti:

set -e
set -u
set -o pipefail

Dan sumbernya di skrip lain. Namun, sementara pipefail akan mengambil:

false | echo it kept going | true

Itu tidak akan mengambil:

echo The output is '`false; echo something else`' 

Outputnya adalah

Outputnya adalah ''

Salah mengembalikan status bukan nol, dan tidak ada stdout. Dalam sebuah pipa itu akan gagal, tetapi di sini kesalahan tidak tertangkap. Ketika ini sebenarnya perhitungan yang disimpan dalam variabel untuk nanti, dan nilainya diatur ke kosong, ini kemudian dapat menyebabkan masalah di kemudian hari.

Jadi - apakah ada cara untuk mendapatkan bash untuk memperlakukan kode pengembalian non-nol di dalam backtick sebagai alasan yang cukup untuk keluar?

Danny Staple
sumber

Jawaban:

51

Bahasa yang tepat digunakan dalam spesifikasi UNIX Tunggal untuk menggambarkan artiset -e adalah:

Ketika opsi ini aktif, jika perintah sederhana gagal karena salah satu alasan yang tercantum dalam Konsekuensi Kesalahan Shell atau mengembalikan nilai status keluar> 0, dan bukan [perintah kondisional atau negasi], maka shell akan segera keluar.

Ada ambiguitas tentang apa yang terjadi ketika perintah seperti itu terjadi dalam subkulit . Dari sudut pandang praktis, semua yang dapat dilakukan subshell adalah keluar dan mengembalikan status bukan nol ke shell induk. Apakah shell induk akan pada gilirannya keluar tergantung pada apakah status bukan nol ini diterjemahkan menjadi perintah sederhana yang gagal di shell induk.

Satu kasus bermasalah seperti itu adalah yang Anda temui: status pengembalian yang bukan nol dari substitusi perintah . Karena status ini diabaikan, itu tidak menyebabkan shell induk untuk keluar. Seperti yang sudah Anda temukan , cara untuk mengambil status keluar ke akun adalah dengan menggunakan substitusi perintah dalam penugasan sederhana : maka status keluar dari penugasan adalah status keluar dari substitusi perintah terakhir dalam penugasan .

Perhatikan bahwa ini akan berfungsi sebagaimana dimaksud hanya jika ada substitusi perintah tunggal, karena hanya status substitusi terakhir yang diperhitungkan. Misalnya, perintah berikut ini berhasil (baik sesuai dengan standar dan dalam setiap implementasi yang saya lihat):

a=$(false)$(echo foo)

Kasus lain yang harus diperhatikan adalah subshells eksplisit : (somecommand). Menurut interpretasi di atas, subshell dapat mengembalikan status bukan nol, tetapi karena ini bukan perintah sederhana di shell induk, shell induk harus melanjutkan. Faktanya, semua cangkang yang saya tahu membuat orang tua kembali pada saat ini. Meskipun ini berguna dalam banyak kasus seperti di (cd /some/dir && somecommand)mana tanda kurung digunakan untuk menjaga operasi seperti perubahan direktori saat ini lokal, itu melanggar spesifikasi jika set -edimatikan dalam subkulit, atau jika subkulit mengembalikan status bukan nol dengan cara yang tidak akan menghentikannya, seperti menggunakan !perintah yang benar. Misalnya, semua abu, bash, pdksh, ksh93, dan zsh keluar tanpa menampilkan foocontoh berikut:

set -e; (set +e; false); echo "This should be displayed"
set -e; (! true); echo "This should be displayed"

Namun tidak ada perintah sederhana yang gagal saat set -ediberlakukan!

Kasus bermasalah ketiga adalah elemen dalam pipa nontrivial . Dalam praktiknya, semua kerang mengabaikan kegagalan elemen-elemen pipa selain yang terakhir, dan menunjukkan salah satu dari dua perilaku mengenai elemen pipa terakhir:

  • ATT ksh dan zsh, yang menjalankan elemen terakhir dari pipeline di shell induk, berbisnis seperti biasa: jika perintah sederhana gagal di elemen terakhir pipeline, shell mengeksekusi perintah itu, yang kebetulan adalah shell induk, keluar.
  • Kerang lain memperkirakan perilaku dengan keluar jika elemen terakhir dari pipa mengembalikan status bukan nol.

Seperti sebelumnya, mematikan set -eatau menggunakan negasi pada elemen terakhir dari pipa menyebabkannya mengembalikan status bukan-nol dengan cara yang seharusnya tidak menghentikan shell; kerang selain ATT ksh dan zsh akan keluar.

pipefailOpsi Bash menyebabkan pipa keluar segera di bawah set -ejika salah satu elemennya mengembalikan status bukan nol.

Perhatikan bahwa sebagai komplikasi lebih lanjut, bash dimatikan set -edalam subkulit kecuali dalam mode POSIX ( set -o posixatau ada POSIXLY_CORRECTdi lingkungan saat bash dimulai).

Semua ini menunjukkan bahwa spesifikasi POSIX sayangnya melakukan pekerjaan yang buruk dalam menentukan -eopsi. Untungnya, cangkang yang ada sebagian besar konsisten dalam perilaku mereka.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Terima kasih untuk ini. Pengalaman yang saya miliki adalah bahwa beberapa kesalahan yang tertangkap dalam versi bash yang lebih baru, diabaikan pada versi sebelumnya dengan set -e. Maksud saya di sini adalah untuk memperkeras skrip sejauh kondisi kesalahan / pengembalian kesalahan yang tidak ditangani akan menyebabkan skrip keluar. Ini adalah sistem warisan yang telah dikenal menghasilkan file keluaran sampah dan kode keluar "0" setelah setengah jam kekacauan dengan env yang salah - kecuali Anda menonton output seperti elang (dan tidak semua kesalahan) ada di stderr, ada yang di stdout, dan ada bagian yang / dev / null'd), Anda tidak tahu.
Danny Staple
"Sebagai contoh, semua ash, bash, pdksh, ksh93 dan zsh keluar tanpa menampilkan foo pada contoh berikut": BusyBox ash tidak berlaku seperti itu, ia menampilkan output sesuai spesifikasi. (Diuji dengan BusyBox 1.19.4.) Juga tidak keluar dengan set -e; (cd /nonexisting).
dubiousjim
27

(Menjawab pertanyaan saya sendiri karena saya sudah menemukan solusinya). Salah satu solusinya adalah selalu menetapkan ini ke variabel perantara. Dengan cara ini kode pengembalian ( $?) diatur.

Begitu

ABC=`exit 1`
echo $?

Akan menampilkan 1(atau sebaliknya keluar jika set -eada), namun:

echo `exit 1`
echo $?

Akan ditampilkan 0setelah baris kosong. Kode return dari echo (atau perintah lain yang dijalankan dengan output backtick) akan menggantikan kode 0 return.

Saya masih terbuka untuk solusi yang tidak memerlukan variabel perantara, tetapi ini membuat saya mendapatkan beberapa cara.

Danny Staple
sumber
23

Seperti yang ditunjukkan OP dalam jawabannya sendiri, menugaskan output dari sub-perintah ke variabel benar-benar menyelesaikan masalah; yang $?tersisa tanpa cedera.

Namun, satu kasus tepi masih dapat membingungkan Anda dengan negatif palsu (mis. Perintah gagal tetapi kesalahan tidak muncul), localdeklarasi variabel:

local myvar=$(subcommand)akan selalu kembali 0!

bash(1) tunjukkan ini:

   local [option] [name[=value] ...]
          ... The return status is 0 unless local is used outside a function,
          an invalid name is supplied, or name is a readonly variable.

Berikut ini adalah contoh uji sederhana:

#!/bin/bash

function test1() {
  data1=$(false) # undeclared variable
  echo 'data1=$(false):' "$?"
  local data2=$(false) # declaring and assigning in one go
  echo 'local data2=$(false):' "$?"
  local data3
  data3=$(false) # assigning a declared variable
  echo 'local data3; data3=$(false):' "$?"
}

test1

Hasil:

data1=$(false): 1
local data2=$(false): 0
local data3; data3=$(false): 1
mogsie
sumber
terima kasih, ketidakkonsistenan antara VAR=...dan local VAR=...benar - benar membuatku bingung!
Jonny
13

Seperti yang orang lain katakan, localakan selalu kembali 0. Solusinya adalah mendeklarasikan variabel terlebih dahulu:

function testcase()
{
    local MYRESULT

    MYRESULT=$(false)
    if (( $? != 0 )); then
        echo "False returned false!"
        return 1
    fi

    return 0
}

Keluaran:

$ testcase
False returned false!
$ 
Akan
sumber
4

Untuk keluar dari kegagalan substitusi perintah, Anda dapat secara eksplisit mengatur -esubshell, seperti ini:

set -e
x=$(set -e; false; true)
echo "this will never be shown"
errr
sumber
1

Poin menarik!

Saya tidak pernah sengaja menemukan itu, karena saya bukan teman set -e(Sebaliknya saya lebih suka trap ... ERR) tetapi sudah diuji bahwa: trap ... ERRjuga tidak menangkap kesalahan dalam $(...)(atau backticks mode lama).

Saya pikir masalahnya adalah (seperti yang sering terjadi) bahwa di sini sebuah subkulit disebut dan -esecara eksplisit berarti shell saat ini .

Hanya solusi lain yang muncul saat ini adalah menggunakan baca:

 ls -l ghost_under_bed | read name

Ini melempar ERRdan dengan -eshell akan dihentikan. Satu-satunya masalah: ini hanya berfungsi untuk perintah dengan satu garis output (atau Anda menyalurkan melalui sesuatu yang bergabung dengan garis).

ktf
sumber
1
Tidak yakin dengan trik baca, mencoba itu menyebabkan variabel tidak terikat. Saya pikir ini mungkin karena sisi lain dari pipa secara efektif adalah sebuah subkulit, nama tidak akan tersedia.
Danny Staple