Cara mempertahankan status keluar terakhir setelah pengujian

8

Apakah mungkin untuk menjaga status keluar perintah terakhir ( $?) tidak berubah setelah pengujian?

Misalnya, saya ingin melakukan:

command -p sudo ...
[ $? -ne 1 ] && exit $?

Yang terakhir exit $?harus mengembalikan status keluar sudo, tetapi sebaliknya ia selalu mengembalikan 0(kode keluar dari tes).

Apakah mungkin untuk melakukan itu tanpa variabel sementara?

Contoh lain untuk memperjelas lebih lanjut:

 spd-say "$@"
 [ $? -ne 127 ] && exit $?

Dalam hal ini saya ingin keluar hanya jika perintah pertama ditemukan (kode keluar! = 127). Dan saya ingin keluar dengan spd-saykode keluar yang sebenarnya (mungkin tidak 0).

EDIT: Saya lupa menyebutkan bahwa saya lebih suka solusi pengaduan POSIX untuk portabilitas yang lebih baik.

Saya menggunakan konstruksi ini dalam skrip di mana saya ingin memberikan alternatif untuk perintah yang sama. Misalnya, lihat skrip crc32 saya .

Masalah dengan variabel sementara adalah mereka bisa membayangi variabel lain, dan untuk menghindari itu Anda harus menggunakan nama panjang, yang tidak baik untuk keterbacaan kode.

eadmaster
sumber
Tidak, tetapi Anda bisa melakukan if ! command -p sudo; then exit; fiyang memiliki hasil yang sama untuk contoh Anda.
jordanm
ok, bagaimana jika saya ingin menguji kode 127 sebagai gantinya? (mis. [ $? -ne 127 ] && exit $?)
eadmaster
1
@jordanm: Benarkah? Jika OP telah mempresentasikan kode / logika yang dia maksud, dan jika saya membacanya dengan benar, dia ingin skrip keluar jika sudoperintah berhasil (yaitu, jika sudokeluar dengan status 0). Tapi, dalam kode Anda, skrip tetap berjalan (tidak keluar) jika sudoberhasil
G-Man Mengatakan 'Reinstate Monica'
@ G-Man - Saya cukup yakin kondisi penanya sebenarnya didasarkan pada kembalinya commanddan tidak sudosama sekali. Itulah yang dimaksud dengan saya ingin keluar hanya jika perintah pertama ditemukan (kode keluar! = 127) dan merupakan pengembalian yang ditentukancommand ketika perintah yang diminta tidak ditemukan. Saya kira masalahnya adalah bahwa memohon sudosebagai bagian dari tes memungkinkan untuk sudomenekan kembali commanddi tempat pertama dan jadi miring tes.
mikeserv

Jawaban:

3

$_akan bekerja di (setidaknya) interaktif dash, bash, zsh, ksh (meskipun tampaknya tidak dalam sebuah pernyataan kondisional seperti yang diminta) dan mkshkerang. Dari mereka - setahu saya - hanya bashdan zshakan mengisi itu dalam shell scripted. Ini bukan parameter POSIX - tetapi cukup portabel untuk shell interaktif modern.

Untuk solusi yang lebih portabel yang dapat Anda lakukan:

command -p sudo ...
eval '[ "$?" = 127 ] || exit '"$?"

Yang pada dasarnya memungkinkan Anda untuk memperluas nilai awal $?ke bagian ujung skrip sebelum bahkan menguji nilainya di kepalanya.

Tapi bagaimanapun, karena Anda tampaknya menguji apakah perintah tersebut sudodapat ditemukan dalam string -p path portabel bawaan shell command, saya akan berpikir Anda bisa melakukannya secara langsung. Juga, hanya untuk menjadi jelas, command tidak akan menguji lokasi argumen untuk sudo- jadi hanya sudo- dan tidak ada yang diminta - yang relevan dengan nilai pengembalian.

Lagi pula, jika itu yang Anda coba lakukan:

command -pv sudo >/dev/null || handle_it
command -p  sudo something or another

... akan berfungsi dengan baik sebagai tes tanpa ada kemungkinan kesalahan dalam sudomenjalankan perintah kembali sedemikian rupa yang dapat membelokkan hasil pengujian Anda.

mikeserv
sumber
9

Ada berbagai opsi untuk menangani status keluar secara andal tanpa overhead, tergantung pada persyaratan aktual.

Anda dapat menyimpan status keluar menggunakan variabel:

command -p sudo ...
rc=$?
[ "$rc" -ne 1 ] && echo "$rc"

Anda dapat langsung memeriksa keberhasilan atau kegagalan:

if  command -p sudo ...
then  echo success
else  echo failure
fi

Atau gunakan casekonstruk untuk membedakan status keluar:

command -p sudo ...
case $? in
(1) ... ;;
(127) ... ;;
(*) echo $? ;;
esac

dengan kasus khusus yang ditanyakan dalam pertanyaan:

command -p sudo ...
case $? in (1) :;; (*) echo $?;; esac

Semua opsi tersebut memiliki keunggulan yang sesuai dengan standar POSIX.

(Catatan: Untuk ilustrasi saya menggunakan echoperintah di atas; ganti dengan exitpernyataan yang sesuai untuk mencocokkan fungsi yang diminta.)

Janis
sumber
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
terdon
1
(Saya ingin tahu mengapa komentar terakhir tidak pindah ke obrolan.) - Seperti yang sudah dijelaskan dan didukung oleh kutipan POSIX dalam diskusi obrolan (lihat detailnya di sana); 1.) ("$(get_errnos)")adalah tambahan buatan substitusi perintah di mana perbandingan dengan kode kesalahan aktual diminta dalam pertanyaan, 2.) Ada jelas dalam standar apa yang berubah $?(dan dengan demikian apa yang tidak mengubahnya), dan 3.) ada tidak ada efek samping dengan konstruk itu (kecuali, seperti yang Anda lakukan, Anda memperkenalkannya secara tidak perlu). - OTOH, karena Anda keberatan, jawaban lain yang Anda sukai jelas tidak standar.
Janis
1
@ mikeserv; Ini menjengkelkan (dan menyesatkan!) Bukan hanya karena Anda menerapkan standar ganda ! Jika Anda menerapkan $(get_errnos)kode buatan yang sama ke solusi lain ( ( exit 42 ); test "$(get_errnos)" -ne $? && echo $_) mereka juga tidak berfungsi. (Anda lebih suka membawa solusi standar saya dalam kesalahan kredit, bukan peretasan non-standar lainnya .) - Tentu saja Anda dapat menambahkan kode arbitrer ke setiap jawaban dan merusaknya. - Dan WRT untuk perubahan $?; hanya karena Anda tidak dapat menemukannya bukan berarti itu tidak benar. (Saya sudah memberikan dalam diskusi kami bahkan kata kunci untuk dicari di POSIX.)
Janis
1
@ mikeserv; Tapi ini, sekali lagi, rawan untuk melanjutkan diskusi (tanpa hasil dan tanpa akhir), yang seharusnya tidak ditempatkan di sini seperti @terdon diamati dengan benar.
Janis
1
@ mikeserv; Saya katakan cukup di awal di mana Anda pertama kali menyebutkan zsh( disebutkan pertama saja; kemudian Anda menambahkan juga dash) bahwa saya pikir itu pasti bug di sana, mengingat bahwa casestatus keluar dan pengaturan $?tampaknya didefinisikan dengan baik dalam POSIX. - Dan juga di sini, sekali lagi, Anda menerapkan standar ganda; peretasan lainnya, misalnya, bukan merupakan standar, juga tidak dapat dipercaya bekerja di shell standar lainnya (misalnya tidak dalam ksh). - Proposal saya standar dan berfungsi bash(kebanyakan digunakan di Linux) dan ksh(shell dominan di Unix komersial).
Janis
7
command -p ...
test 1 -ne $? && exit $_

Gunakan $_, yang memperluas argumen terakhir dari perintah sebelumnya.

llua
sumber
1
Berlaku untuk contoh khusus ini, tetapi hanya dapat digunakan jika tidak ada perintah lain di antara $?dan $_referensi. IMHO lebih baik untuk tetap pada metode yang konsisten yang bekerja dalam kasus lain (dan juga dapat membantu dengan keterbacaan kode).
Dan Cornilescu
4
Shell secara khusus dinamai dua kali, sekali dalam judul dan sekali sebagai tag. Portabilitas juga tidak disebutkan di mana pun dalam pertanyaan, jadi saya memberikan jawaban yang berfungsi di shell tersebut. Apa batasan aneh lain yang akan dibuat?
llua
1
@ mikeserv; Jika Anda melewatkannya: Saya berkata: "Ada komentar pertama di sini tentang POSIX." yang harus diperbaiki. Tidak lebih, tidak kurang. - Sebagaimana diperdebatkan secara menyeluruh dengan Anda dan dijelaskan di sana, ketiga saran dalam jawaban lainnya didefinisikan dengan baik oleh POSIX. Tidak perlu mengulangi pendapat (salah IMO) Anda di sini, atau memulai iterasi sengketa lainnya.
Janis
1
@ mikeserv; Efek samping ekspansi dalam case pola , satu-satunya tempat yang penting dalam teori (tetapi tidak dalam pertanyaan yang diberikan), adalah kasus yang dikonstruksi. - Bagaimanapun, substitusi perintah shell Anda akan menyebarkan hasil dari perintah yang disematkan, biasanya ekspansi lainnya tidak akan memengaruhi status pengembalian jika tidak ada perintah yang terlibat yang dapat membuat kesalahan ( x=${a!b}kasus pikiran , tetapi tidak relevan di sini). - Apa yang Anda maksud dengan "perintah tanpa nama perintah" ?
Janis
1
@ mikeserv; Itu hanya dapat dikalahkan oleh ad hoc yang merupakan kode yang tidak perlu. Sama sekali tidak ada area abu-abu jika Anda mengambil saran tanpa harus memperkenalkan omong kosong buatan yang tidak perlu. Persyaratannya sangat jelas dalam hal ini: 1. menjalankan perintah, 2. memeriksa kode keluar, 3. mengembalikan kode keluar. - Apakah kamu mengerti? - Anda mengubah persyaratan itu secara sewenang-wenang hanya untuk membuat argumen.
Janis
6

Anda dapat mendefinisikan (dan menggunakan) fungsi shell:

check_exit_status()
{
    [ "$1" -ne 1 ] && exit "$1"
}

Kemudian

command -p sudo ...
check_exit_status "$?"

Bisa dibilang, ini "curang", karena itu membuat salinan $?dalam check_exit_statusdaftar argumen.

Ini mungkin terlihat agak canggung, dan memang begitu. (Itu kadang-kadang terjadi ketika Anda memaksakan batasan sewenang-wenang pada masalah.) Ini mungkin tampak tidak fleksibel, tetapi tidak. Anda dapat membuat check_exit_statuslebih kompleks, menambahkan argumen yang memberi tahu tes apa yang harus dilakukan pada nilai status keluar.

Scott
sumber
0

Untuk menjawab pertanyaan langsung Anda, tidak, tidak mungkin untuk tetap $?tidak berubah. Dan ini adalah salah satu kasus di mana saya curiga Anda berfokus pada masalah yang salah. Variabel sementara adalah cara standar dan disukai untuk mendapatkan efek yang Anda cari. Ini sangat standar sehingga saya menyarankan untuk meninggalkan (atau memikirkan kembali) alasan apa pun yang Anda pikir Anda miliki karena tidak ingin menggunakannya; Saya sangat ragu bahwa itu sepadan dengan kompleksitas tambahan yang ditambahkan oleh metode lain.

Jika Anda hanya bertanya karena penasaran, maka jawabannya adalah tidak.

David Z
sumber
@ mikeserv Saya tidak yakin saya mengerti - apakah Anda berbicara tentang menetapkan secara manual $?atau sesuatu seperti itu
David Z
0

Berikut cuplikan kode saya untuk mempertahankan status keluar sebelumnya tanpa keluar dari skrip / shell saat ini

EXIT_STATUS=1
if [[ $EXIT_STATUS == 0 ]]
then
  echo yes
else
  (exit $EXIT_STATUS)
fi
Junaid
sumber