web-dev-qa-db-ja.com

コマンドの長いリストを実行し、問題が発生した場合にメッセージを表示する便利な方法は何ですか?

ほとんどのLinuxガイドは、「実行する必要があるcommand_1、次にcommand_2、次にcommand_3 "など。すべてを手動で実行して時間を無駄にしたくないので、スクリプトを作成します

command_1
command_2
command_3

一度実行します。しかし、多くの場合、一部のコマンドは失敗し、どのコマンドが失敗したのかはわかりません。また、通常、何かが以前に失敗した場合、残りのコマンドはすべて意味がありません。したがって、より良いスクリプトは次のようなものになります

   (command_1 && echo OK command_1 || (echo FAILED command_1; false) )
&& (command_2 && echo OK command_2 || (echo FAILED command_2; false) )
&& (command_3 && echo OK command_3 || (echo FAILED command_3; false) )
&& echo DONE 
|| echo FAILED

ただし、ボイラープレートコードを多く記述し、各コマンドを3回繰り返す必要があります。また、中括弧の一部を誤入力する可能性が高すぎます。最後のスクリプトが実行するより便利な方法はありますか?特に:

  • コマンドを順番に実行する
  • コマンドが失敗した場合に中断する
  • 書き込み、失敗したコマンド(ある場合)
  • コマンドとの通常の対話を許可します。すべての出力を出力し、コマンドが何かを要求した場合はキーボードからの入力を許可します。

回答の概要(2020年1月2日)

ソリューションには2つのタイプがあります。

  • コマンドを変更せずにガイドからコピーして貼り付けることができますが、失敗したコマンドは最後に出力されません。したがって、失敗したコマンドが非常に長い出力を生成した場合、何のコマンドが失敗したかを確認するには、多くの行を上にスクロールする必要があります。 (すべてのトップ回答)
  • 失敗したコマンドを最後の行に出力しますが、引用符を追加するか(Johnによる回答)、またはtryステートメントを追加して、チェーンされたコマンドを個別に分割することにより、コマンドをコピーして貼り付けた後、コマンドを変更する必要があります。もの(ジェイセンによる答え)。

あなたは人々を揺るがしますが、私はこの質問をしばらく開いたままにしておきます。たぶん、両方のニーズを満たすソリューションを知っている人がいるかもしれません(最後の行に失敗したコマンドを出力し、変更せずにコマンドをコピーして貼り付けることができます)。

33
Arqwer

1つのオプションは、コマンドをbashスクリプトに入れ、set -eで開始することです。

これにより、コマンドがゼロ以外の終了ステータスで終了した場合、スクリプトが早期に終了します。

スタックオーバーフローに関するこの質問もご覧ください: https://stackoverflow.com/q/19622198/82819

エラーを出力するには、次を使用できます

trap 'do_something' ERR

ここで、do_somethingは、エラーを表示するために作成するコマンドです。

これがどのように機能するかを確認するスクリプトの例です。

#!/bin/bash

set -e
trap 'echo "******* FAILED *******" 1>&2' ERR

echo 'Command that succeeds'   # this command works
ls non_existent_file           # this should fail
echo 'Unreachable command'     # and this is never called
                               # due to set -e

そしてこれは出力です:

$ ./test.sh 
Command that succeeds
ls: cannot access 'non_existent_file': No such file or directory
******* FAILED *******

また、@ jickで述べたように、パイプラインの終了ステータスはデフォルトでfinalコマンド。これは、パイプライン内の非最終コマンドが失敗した場合、set -eによってキャッチされないことを意味します。この問題に関心がある場合は、set -o pipefailを使用して修正できます。


提案されたように、関数を使用して、私の@ glenn jackmanおよび@ Monty Harderネストされた引用を回避するため、ハンドラーはスクリプトを読みやすくすることができます。とにかく関数を使用しているので、set -eを完全に削除し、ハンドラーでexit 1を使用しました。

#!/bin/bash

error_handler() {
  echo "******* FAILED *******" 1>&2
  exit 1
}

trap error_handler ERR

echo 'Command that succeeds'   # this command works
ls non_existent_file           # this should fail
echo 'Unreachable command'     # and this is never called
                               # due to the exit in the handler

スクリプトの終了ステータスは異なりますが、出力は上記と同じです。

62
user000001

異常な解決策が必要ですか? makeをインストールしている場合は、コマンドのリストをMakefileに入れてmakeを実行できます。追加された利点:エラーが発生したかどうかを確認する必要がありません。 makeは、各コマンドの戻り値を自動的にチェックします。ゼロ以外の場合、レシピはエラーで終了します。特定のコマンドのエラーを無視したい場合は、|| true

Makefileの例:

.PHONY: all
.SILENT:

all:
    echo "Started list of commands."
    true
    echo "Executing a command which will fail, but I want to ignore failure."
    false || true
    echo "Executing a command which will definitely fail."
    false
    echo "This code will not be reached."

注:上記のようにコマンドをインデントする(heh)ことを確認してください。

20

これはbashでのみ機能すると思いますが、以下を試すことができます。

set -o xtrace
set -o errexit

または、簡潔になりたい場合は、

set -ex

これは2つのことを行います:errexit(-e)はエラーが発生したときにスクリプトを中止し、xtrace(-x)はbashが実行する直前に各コマンドを出力します。失敗した場合は、それが何を実行していたかを正確に把握できます。

1つの欠点は、出力が乱雑になることですが、それで問題がなければ、これは最小限の作業でかなり良いソリューションになります。

  • その間、set -o pipefailも含めることをお勧めします。それ以外の場合、foo | barを実行すると、fooの失敗は通知なく無視されます。 (警告:パイプステートメントの「終了コード」を微妙に変更するため、他の誰かのスクリプトを変更する場合は注意して使用してください。また、fooの失敗に実際には気にしない場合があるので、明らかにそのような場合、pipefailは使用できません。)
16
jick

あなたはこのようなことをすることができます:

$ for f in command_1 command_2 command_3 command_4
do
  $f
  rc=$?
  if [[ 0 != $rc ]]; then echo Failed command: ${f}; break; fi
done

これは、コマンドにオプションがないことを前提としています。コマンドがある場合は、各コマンド/オプションセットを引用符で囲む必要があります。

3
John
failed=0
try(){
(( failed )) && return
"$@" 
ec=$?
if (( ec ))
then
  failed=$ec
  echo "failed($failed): $*" >&2
fi
}

try something args
try other-thing more args

if (( failed )) 
then
  echo "something went wrong: see above." >&2
  exit 1
fi
3
Jasen

対話型シェルで実行する場合は、このソリューションを使用します。

set -x; command_1 && command_2 && command_3 && echo "Everything OK" || echo "Error while executing last command"; set +x

すべてのコマンドが成功した場合の出力:

+ command_1
output of command 1
+ command_2
output of command 2
+ command_3
output of command 3
+ echo "Everything OK"
Everything OK
+ set +x

command_2が失敗した場合の出力:

+ command_1
output of command 1
+ command_2
output of command 2
+ echo "Error while executing last command"
+ set +x

set -xコマンドは、実行されたすべてのコマンドのロギングを有効にします。ログに記録されたコマンドには、1つ以上のプラス記号が付加されます(スタイルは、変数$PS4を使用して変更できます)。 set +xはこのモードを無効にします。

set -xall実行されたコマンドのロギングを有効にするため、set +xset -xを同じ行に配置する必要があります— $COMMAND_Promptおよびそれによって実行されたコマンドを含みます。


シェルスクリプトにも使用できます。

#!/bin/bash -ex

command_1
command_2
command_3

シェルオプションは、Shebang行に埋め込むことができます。 -eは、最初に失敗したコマンドの後にシェルを終了させます(すべての行が&&で終わる場合など)。 -xは、コマンドのロギングを有効にします。インタプリタに渡すことができるのはone引数のみであることに注意してください。 /usr/bin/envを使用して複数の引数を渡します(#!/usr/bin/env -S bash -ex arg1 arg2 argN)。

3
jiwopene

たぶん、あなたがすでにやっていることのための単なる関数:

function run_cmd
    ( $* && echo OK $* || ( echo FAILED $*; false ) )

run_cmd echo 1 &&
run_cmd false 2 &&
run_cmd echo 3 &&
echo DONE || echo FAILED

これの出力は:

1
OK echo 1
FAILED false 2
FAILED
1
Pedja