Apakah ada sesuatu yang mirip dengan pipefail untuk beberapa perintah, seperti 'mencoba' pernyataan tetapi dalam bash. Saya ingin melakukan sesuatu seperti ini:
echo "trying stuff"
try {
command1
command2
command3
}
Dan pada setiap titik, jika ada perintah gagal, drop out, dan echo kesalahan perintah itu. Saya don't ingin melakukan sesuatu seperti:
command1
if [ $? -ne 0 ]; then
echo "command1 borked it"
fi
command2
if [ $? -ne 0 ]; then
echo "command2 borked it"
fi
Dan sebagainya... atau sesuatu seperti:
pipefail -o
command1 "arg1" "arg2" | command2 "arg1" "arg2" | command3
Karena argumen dari masing-masing perintah saya percaya (benar saya jika saya'm salah) akan mengganggu satu sama lain. Dua metode ini tampak mengerikan bertele-tele dan jahat kepada saya sehingga saya'm di sini menarik untuk metode yang lebih efisien.
Anda dapat menulis fungsi yang meluncurkan dan tes perintah untuk anda. Menganggap command1
dan command2
adalah variabel lingkungan yang telah ditetapkan untuk sebuah perintah.
function mytest {
"$@"
local status=$?
if [ $status -ne 0 ]; then
echo "error with $1" >&2
fi
return $status
}
mytest $command1
mytest $command2
Apa yang anda maksud dengan "drop out dan echo kesalahan"? Jika anda berarti anda ingin script untuk menghentikan segera setelah setiap perintah gagal, maka lakukan saja
set -e # DON'T do this. See commentary below.
di mulai dari script (tapi perhatikan peringatan di bawah ini). Jangan repot-repot menggemakan pesan kesalahan: membiarkan gagal perintah menangani itu. Dengan kata lain, jika anda melakukannya:
#!/bin/sh
set -e # Use caution. eg, don't do this
command1
command2
command3
dan command2 gagal, saat mencetak pesan kesalahan ke stderr, maka tampaknya bahwa anda telah mencapai apa yang anda inginkan. (Kecuali saya salah menafsirkan apa yang anda inginkan!)
Sebagai akibat wajar, setiap perintah yang anda tulis harus berperilaku baik: harus laporan kesalahan ke stderr bukan stdout (kode contoh pada pertanyaan cetakan kesalahan ke stdout) dan itu harus keluar dengan non-zero status ketika gagal.
Namun, saya tidak lagi menganggap hal ini menjadi sebuah praktek yang baik. set-e
telah berubah semantik dengan versi yang berbeda dari bash, dan meskipun itu bekerja dengan baik untuk sebuah script sederhana, ada begitu banyak kasus tepi yang pada dasarnya tidak dapat digunakan. (Mempertimbangkan hal-hal seperti: set-e; foo() { false; echo tidak harus cetak; } ; foo && echo ok
semantik di sini agak masuk akal, tapi jika anda refactor kode menjadi satu fungsi yang bergantung pada pilihan pengaturan untuk mengakhiri lebih awal, anda dapat dengan mudah digigit.) IMO lebih baik untuk menulis:
#!/bin/sh
command1 || exit
command2 || exit
command3 || exit
atau
#!/bin/sh
command1 && command2 && command3
Saya memiliki satu set fungsi skrip yang saya gunakan secara luas di Red Hat system. Mereka menggunakan sistem fungsi dari /etc/init.d/fungsi
untuk mencetak hijau [ OK ]
merah [GAGAL]
indikator status.
Secara opsional, anda dapat mengatur $LOG_STEPS
variabel untuk log nama file jika anda ingin log perintah yang gagal.
step "Installing XFS filesystem tools:"
try rpm -i xfsprogs-*.rpm
next
step "Configuring udev:"
try cp *.rules /etc/udev/rules.d
try udevtrigger
next
step "Adding rc.postsysinit hook:"
try cp rc.postsysinit /etc/rc.d/
try ln -s rc.d/rc.postsysinit /etc/rc.postsysinit
try echo $'\nexec /etc/rc.postsysinit' >> /etc/rc.sysinit
next
Installing XFS filesystem tools: [ OK ]
Configuring udev: [FAILED]
Adding rc.postsysinit hook: [ OK ]
#!/bin/bash
. /etc/init.d/functions
# Use step(), try(), and next() to perform a series of commands and print
# [ OK ] or [FAILED] at the end. The step as a whole fails if any individual
# command fails.
#
# Example:
# step "Remounting / and /boot as read-write:"
# try mount -o remount,rw /
# try mount -o remount,rw /boot
# next
step() {
echo -n "$@"
STEP_OK=0
[[ -w /tmp ]] && echo $STEP_OK > /tmp/step.$$
}
try() {
# Check for `-b' argument to run command in the background.
local BG=
[[ $1 == -b ]] && { BG=1; shift; }
[[ $1 == -- ]] && { shift; }
# Run the command.
if [[ -z $BG ]]; then
"$@"
else
"$@" &
fi
# Check if command failed and update $STEP_OK if so.
local EXIT_CODE=$?
if [[ $EXIT_CODE -ne 0 ]]; then
STEP_OK=$EXIT_CODE
[[ -w /tmp ]] && echo $STEP_OK > /tmp/step.$$
if [[ -n $LOG_STEPS ]]; then
local FILE=$(readlink -m "${BASH_SOURCE[1]}")
local LINE=${BASH_LINENO[0]}
echo "$FILE: line $LINE: Command \`$*' failed with exit code $EXIT_CODE." >> "$LOG_STEPS"
fi
fi
return $EXIT_CODE
}
next() {
[[ -f /tmp/step.$$ ]] && { STEP_OK=$(< /tmp/step.$$); rm -f /tmp/step.$$; }
[[ $STEP_OK -eq 0 ]] && echo_success || echo_failure
echo
return $STEP_OK
}
Untuk apa itu's worth, dengan cara yang lebih singkat untuk menulis kode untuk memeriksa setiap perintah untuk sukses adalah:
command1 || echo "command1 borked it"
command2 || echo "command2 borked it"
It's masih membosankan tapi setidaknya itu's mudah dibaca.
Alternatif lain adalah hanya untuk bergabung perintah bersama-sama dengan &&
jadi yang pertama untuk gagal mencegah sisa dari pelaksana:
command1 &&
command2 &&
command3
Ini isn't sintaks yang anda minta dalam pertanyaan, tapi itu's pola umum untuk kasus penggunaan yang anda gambarkan. Secara umum perintah yang harus bertanggung jawab untuk pencetakan kegagalan sehingga anda don't harus melakukannya secara manual (mungkin dengan q
bendera untuk membungkam kesalahan ketika anda don't ingin mereka). Jika anda memiliki kemampuan untuk memodifikasi perintah-perintah ini, aku'd mengedit mereka untuk berteriak pada kegagalan, bukan membungkus mereka dalam sesuatu yang lain yang tidak begitu.
Perhatikan juga bahwa anda don't perlu anda lakukan:
command1
if [ $? -ne 0 ]; then
Anda hanya bisa mengatakan:
if ! command1; then
Alih-alih menciptakan runner fungsi atau menggunakan set-e
, gunakan perangkap
:
trap 'echo "error"; do_cleanup failed; exit' ERR
trap 'echo "received signal to stop"; do_cleanup interrupted; exit' SIGQUIT SIGTERM SIGINT
do_cleanup () { rm tempfile; echo "$1 $(date)" >> script_log; }
command1
command2
command3
Perangkap bahkan memiliki akses ke nomor baris dan baris perintah perintah yang memicu itu. Variabel $BASH_LINENO
dan $BASH_COMMAND
.
Secara pribadi saya lebih memilih untuk menggunakan ringan pendekatan, seperti yang terlihat di sini;
yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }
asuser() { sudo su - "$1" -c "${*:2}"; }
Contoh penggunaan:
try apt-fast upgrade -y
try asuser vagrant "echo 'uname -a' >> ~/.profile"
I've dikembangkan hampir sempurna try & menangkap pelaksanaan di bash, yang memungkinkan anda untuk menulis kode seperti:
try
echo 'Hello'
false
echo 'This will not be displayed'
catch
echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
Anda bahkan dapat nest try-catch blok di dalam diri mereka sendiri!
try {
echo 'Hello'
try {
echo 'Nested Hello'
false
echo 'This will not execute'
} catch {
echo "Nested Caught (@ $__EXCEPTION_LINE__)"
}
false
echo 'This will not execute too'
} catch {
echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
}
Kode adalah bagian dari saya bash boilerplate/framework. Itu lebih meluas ide try & menangkap dengan hal-hal seperti kesalahan penanganan dengan pelacakan mundur dan pengecualian (ditambah beberapa fitur bagus lainnya).
Berikut ini's kode yang's yang bertanggung jawab hanya untuk mencoba & menangkap:
set -o pipefail
shopt -s expand_aliases
declare -ig __oo__insideTryCatch=0
# if try-catch is nested, then set +e before so the parent handler doesn't catch us
alias try="[[ \$__oo__insideTryCatch -gt 0 ]] && set +e;
__oo__insideTryCatch+=1; ( set -e;
trap \"Exception.Capture \${LINENO}; \" ERR;"
alias catch=" ); Exception.Extract \$? || "
Exception.Capture() {
local script="${BASH_SOURCE[1]#./}"
if [[ ! -f /tmp/stored_exception_source ]]; then
echo "$script" > /tmp/stored_exception_source
fi
if [[ ! -f /tmp/stored_exception_line ]]; then
echo "$1" > /tmp/stored_exception_line
fi
return 0
}
Exception.Extract() {
if [[ $__oo__insideTryCatch -gt 1 ]]
then
set -e
fi
__oo__insideTryCatch+=-1
__EXCEPTION_CATCH__=( $(Exception.GetLastException) )
local retVal=$1
if [[ $retVal -gt 0 ]]
then
# BACKWARDS COMPATIBILE WAY:
# export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-1)]}"
# export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-2)]}"
export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[-1]}"
export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[-2]}"
export __EXCEPTION__="${__EXCEPTION_CATCH__[@]:0:(${#__EXCEPTION_CATCH__[@]} - 2)}"
return 1 # so that we may continue with a "catch"
fi
}
Exception.GetLastException() {
if [[ -f /tmp/stored_exception ]] && [[ -f /tmp/stored_exception_line ]] && [[ -f /tmp/stored_exception_source ]]
then
cat /tmp/stored_exception
cat /tmp/stored_exception_line
cat /tmp/stored_exception_source
else
echo -e " \n${BASH_LINENO[1]}\n${BASH_SOURCE[2]#./}"
fi
rm -f /tmp/stored_exception /tmp/stored_exception_line /tmp/stored_exception_source
return 0
}
Merasa bebas untuk menggunakan, garpu dan berkontribusi - it's pada GitHub.
Maaf bahwa saya tidak dapat membuat komentar untuk jawaban pertama Tetapi anda harus menggunakan instance baru untuk mengeksekusi perintah: cmd_output=$($@)
#!/bin/bash
function check_exit {
cmd_output=$($@)
local status=$?
echo $status
if [ $status -ne 0 ]; then
echo "error with $1" >&2
fi
return $status
}
function run_command() {
exit 1
}
check_exit run_command
Untuk ikan shell pengguna yang tersandung di thread ini.
Mari foo
menjadi fungsi yang tidak "kembalikan" (echo) nilai, tetapi set kode keluar seperti biasa.
Untuk menghindari pemeriksaan $status
setelah memanggil fungsi tersebut, anda dapat melakukan:
foo; and echo success; or echo failure
Dan jika itu's terlalu lama untuk muat pada satu baris:
foo; and begin
echo success
end; or begin
echo failure
end
Ketika saya menggunakan ssh
aku harus berbeda antara masalah-masalah yang disebabkan oleh masalah sambungan dan kode kesalahan perintah remote di errexit
(untuke
) mode. Saya menggunakan fungsi berikut:
# prepare environment on calling site:
rssh="ssh -o ConnectionTimeout=5 -l root $remote_ip"
function exit255 {
local flags=$-
set +e
"$@"
local status=$?
set -$flags
if [[ $status == 255 ]]
then
exit 255
else
return $status
fi
}
export -f exit255
# callee:
set -e
set -o pipefail
[[ $rssh ]]
[[ $remote_ip ]]
[[ $( type -t exit255 ) == "function" ]]
rjournaldir="/var/log/journal"
if exit255 $rssh "[[ ! -d '$rjournaldir/' ]]"
then
$rssh "mkdir '$rjournaldir/'"
fi
rconf="/etc/systemd/journald.conf"
if [[ $( $rssh "grep '#Storage=auto' '$rconf'" ) ]]
then
$rssh "sed -i 's/#Storage=auto/Storage=persistent/' '$rconf'"
fi
$rssh systemctl reenable systemd-journald.service
$rssh systemctl is-enabled systemd-journald.service
$rssh systemctl restart systemd-journald.service
sleep 1
$rssh systemctl status systemd-journald.service
$rssh systemctl is-active systemd-journald.service
Anda dapat menggunakan @john-kugelman 's mengagumkan solusi ditemukan di atas pada non-RedHat sistem oleh komentar keluar garis ini dalam kode nya:
. /etc/init.d/fungsi
Kemudian, paste kode di bawah ini di bagian akhir. Pengungkapan penuh: Ini hanya langsung copy & paste yang relevan bit yang disebutkan di atas file yang diambil dari Centos 7.
Diuji pada MacOS dan Ubuntu 18.04.
``bash
BOOTUP=warna RES_COL=60 MOVE_TO_COL="echo -id \033[${RES_COL}G" SETCOLOR_SUCCESS="echo -id \033[1;32m" SETCOLOR_FAILURE="echo -id \033[1;31m" SETCOLOR_WARNING="echo -id \033[1;33m" SETCOLOR_NORMAL="echo -id \033[0;39m"
echo_success() { [ "$BOOTUP" = "warna" ] && $MOVE_TO_COL echo -n "[" [ "$BOOTUP" = "warna" ] && $SETCOLOR_SUCCESS echo -n $" OK " [ "$BOOTUP" = "warna" ] && $SETCOLOR_NORMAL echo -n "]" echo -ne "\r" return 0 }
echo_failure() { [ "$BOOTUP" = "warna" ] && $MOVE_TO_COL echo -n "[" [ "$BOOTUP" = "warna" ] && $SETCOLOR_FAILURE echo -n $"GAGAL" [ "$BOOTUP" = "warna" ] && $SETCOLOR_NORMAL echo -n "]" echo -ne "\r" kembali 1 }
echo_passed() { [ "$BOOTUP" = "warna" ] && $MOVE_TO_COL echo -n "[" [ "$BOOTUP" = "warna" ] && $SETCOLOR_WARNING echo -n $"LULUS" [ "$BOOTUP" = "warna" ] && $SETCOLOR_NORMAL echo -n "]" echo -ne "\r" kembali 1 }
echo_warning() { [ "$BOOTUP" = "warna" ] && $MOVE_TO_COL echo -n "[" [ "$BOOTUP" = "warna" ] && $SETCOLOR_WARNING echo -n $"PERINGATAN" [ "$BOOTUP" = "warna" ] && $SETCOLOR_NORMAL echo -n "]" echo -ne "\r" kembali 1 } ``
Memeriksa status fungsional cara
assert_exit_status() {
lambda() {
local val_fd=$(echo $@ | tr -d ' ' | cut -d':' -f2)
local arg=$1
shift
shift
local cmd=$(echo $@ | xargs -E ':')
local val=$(cat $val_fd)
eval $arg=$val
eval $cmd
}
local lambda=$1
shift
eval $@
local ret=$?
$lambda : <(echo $ret)
}
Penggunaan:
assert_exit_status 'lambda status -> [[ $status -ne 0 ]] && echo Status is $status.' lls
Output
Status is 127