web-dev-qa-db-ja.com

Bashで実行された最後のコマンドをエコーし​​ますか?

Bashスクリプト内で実行された最後のコマンドをエコーし​​ようとしています。いくつかのhistory,tail,head,sedは、コマンドがパーサーの観点からスクリプト内の特定の行を表す場合に正常に機能します。ただし、状況によっては、コマンドがcaseステートメント内に挿入された場合など、予期した出力が得られません。

スクリプト:

#!/bin/bash
set -o history
date
last=$(echo `history |tail -n2 |head -n1` | sed 's/[0-9]* //')
echo "last command is [$last]"

case "1" in
  "1")
  date
  last=$(echo `history |tail -n2 |head -n1` | sed 's/[0-9]* //')
  echo "last command is [$last]"
  ;;
esac

出力:

Tue May 24 12:36:04 CEST 2011
last command is [date]
Tue May 24 12:36:04 CEST 2011
last command is [echo "last command is [$last]"]

[Q] bashスクリプト内でこのコマンドがどのように/どこで呼び出されるかに関係なく、誰かが最後の実行コマンドをエコーする方法を見つけるのを手伝ってくれますか?

私の答え

私の仲間のSO'ersからの非常に感謝された貢献にもかかわらず、私はrun関数を書くことを選択しました-それは単一のコマンドとしてすべてのパラメータを実行し、失敗するとコマンドとそのエラーコードを表示します-次の利点があります:
-チェックするコマンドの先頭にrunを追加するだけで、コマンドを1行に保持し、スクリプトの簡潔さに影響しません
-これらのコマンドのいずれかでスクリプトが失敗するたびに、スクリプトの最後の出力行は、どのコマンドが失敗したかを終了コードとともに明確に表示するメッセージであり、デバッグを容易にします

サンプルスクリプト:

#!/bin/bash
die() { echo >&2 -e "\nERROR: $@\n"; exit 1; }
run() { "$@"; code=$?; [ $code -ne 0 ] && die "command [$*] failed with error code $code"; }

case "1" in
  "1")
  run ls /opt
  run ls /wrong-dir
  ;;
esac

出力:

$ ./test.sh
apacheds  google  iptables
ls: cannot access /wrong-dir: No such file or directory

ERROR: command [ls /wrong-dir] failed with error code 2

複数の引数、引数としてbash変数、引用符で囲まれた引数を使用してさまざまなコマンドをテストしましたが、run関数はそれらを壊しませんでした。これまでに発見した唯一の問題は、中断するエコーを実行することですが、とにかくエコーをチェックする予定はありません。

71
Max

コマンド履歴はインタラクティブな機能です。完全なコマンドのみが履歴に入力されます。たとえば、case構文は、シェルが解析を終了したときに全体として入力されます。 historyビルトインで履歴を検索することも(Shell拡張(!:p))は、単純なコマンドの呼び出しを出力するという、あなたが望むように見えることを行います。

DEBUG trap を使用すると、単純なコマンド実行の直前にコマンドを実行できます。実行するコマンドの文字列バージョン(スペースで区切られた単語)は、 BASH_COMMAND 変数。

trap 'previous_command=$this_command; this_command=$BASH_COMMAND' DEBUG
…
echo "last command is $previous_command"

ご了承ください previous_commandはコマンドを実行するたびに変わるため、コマンドを使用するために変数に保存します。前のコマンドの戻りステータスも知りたい場合は、両方を1つのコマンドに保存してください。

cmd=$previous_command ret=$?
if [ $ret -ne 0 ]; then echo "$cmd failed with error code $ret"; fi

さらに、失敗したコマンドでのみ中止したい場合は、 set -e 最初に失敗したコマンドでスクリプトを終了します。 EXIT trap から最後のコマンドを表示できます。

set -e
trap 'echo "exit $? due to $previous_command"' EXIT

スクリプトが何をしているかを確認するためにスクリプトをトレースしようとしている場合は、これをすべて忘れて set -x

54
Gilles

Bashには、最後に実行されたコマンドにアクセスする機能が組み込まれています。しかし、これは最後のコマンド全体(例:caseコマンド全体)であり、元々リクエストしたような個々の単純なコマンドではありません。

!:0 =実行されたコマンドの名前。

!:1 =前のコマンドの最初のパラメーター

!:* =前のコマンドのすべてのパラメーター

!:-1 =前のコマンドの最終パラメーター

!! =前のコマンドライン

等.

したがって、質問に対する最も簡単な答えは、実際には次のとおりです。

echo !!

...代わりに:

echo "Last command run was ["!:0"] with arguments ["!:*"]"

自分で試してみてください!

echo this is a test
echo !!

スクリプトでは、履歴の展開はデフォルトでオフになっています。これを有効にする必要があります

set -o history -o histexpand
146
groovyspaceman

answer from Gilles を読んだ後、$BASH_COMMAND varもEXITトラップで使用可能(および目的の値)でした。

したがって、次のbashスクリプトは期待どおりに機能します。

#!/bin/bash

exit_trap () {
  local lc="$BASH_COMMAND" rc=$?
  echo "Command [$lc] exited with code [$rc]"
}

trap exit_trap EXIT
set -e

echo "foo"
false 12345
echo "bar"

出力は

foo
Command [false 12345] exited with code [1]

barは、set -eは、コマンドが失敗し、falseコマンドが常に失敗する場合(定義により)、bashがスクリプトを終了するようにします。 12345に渡されたfalseは、失敗したコマンドの引数もキャプチャされることを示すためだけにあります(falseコマンドは、渡された引数を無視します)

14
Hercynium

メインスクリプトでset -xを使用して(実行されたすべてのコマンドをスクリプトで出力する)、set -xによって生成された出力の最終行のみを表示するラッパースクリプトを作成することで、これを達成できました。 。

これがメインスクリプトです。

#!/bin/bash
set -x
echo some command here
echo last command

そして、これはラッパースクリプトです。

#!/bin/sh
./test.sh 2>&1 | grep '^\+' | tail -n 1 | sed -e 's/^\+ //'

ラッパースクリプトを実行すると、これが出力として生成されます。

echo last command
7
Mark Drago

history | tail -2 | head -1 | cut -c8-999

tail -2は、履歴から最後の2つのコマンド行を返しますhead -1は、最初の行だけを返しますcut -c8-999はコマンドラインのみを返し、PIDとスペースを削除します。

1
Guma

最後のコマンド($ _)変数と最後のエラー($?)変数の間には競合状態があります。それらの1つを独自の変数に格納しようとすると、setコマンドのために両方がすでに新しい値に遭遇しました。実際、この場合、最後のコマンドにはまったく値がありません。

両方の情報を独自の変数に(ほぼ)格納するために行ったのは次のとおりです。そのため、私のbashスクリプトはエラーがあったかどうかを判断し、最後の実行コマンドでタイトルを設定できます。

   # This construct is needed, because of a racecondition when trying to obtain
   # both of last command and error. With this the information of last error is
   # implied by the corresponding case while command is retrieved.

   if   [[ "${?}" == 0 && "${_}" != "" ]] ; then
    # Last command MUST be retrieved first.
      LASTCOMMAND="${_}" ;
      RETURNSTATUS='✓' ;
   Elif [[ "${?}" == 0 && "${_}" == "" ]] ; then
      LASTCOMMAND='unknown' ;
      RETURNSTATUS='✓' ;
   Elif [[ "${?}" != 0 && "${_}" != "" ]] ; then
    # Last command MUST be retrieved first.
      LASTCOMMAND="${_}" ;
      RETURNSTATUS='✗' ;
      # Fixme: "$?" not changing state until command executed.
   Elif [[ "${?}" != 0 && "${_}" == "" ]] ; then
      LASTCOMMAND='unknown' ;
      RETURNSTATUS='✗' ;
      # Fixme: "$?" not changing state until command executed.
   fi

このスクリプトは、エラーが発生した場合に情報を保持し、最後の実行コマンドを取得します。競合状態のため、実際の値を保存できません。その上、ほとんどのコマンドは実際にはエラー番号を気にしません。'0 'とは異なるものを返すだけです。 bashのerrono拡張を使用すると、気付くでしょう。

Bashエクステンションのように、bashの「インターン」スクリプトのようなもので可能になるはずですが、私はそのようなものに精通しておらず、互換性もありません。

補正

両方の変数を同時に取得できるとは思いませんでした。コードのスタイルは好きですが、2つのコマンドとして解釈されると思いました。これは間違っていたので、私の答えは次のようになります。

   # Because of a racecondition, both MUST be retrieved at the same time.
   declare RETURNSTATUS="${?}" LASTCOMMAND="${_}" ;

   if [[ "${RETURNSTATUS}" == 0 ]] ; then
      declare RETURNSYMBOL='✓' ;
   else
      declare RETURNSYMBOL='✗' ;
   fi

私の投稿は肯定的な評価を得られないかもしれませんが、最終的に自分で問題を解決しました。そして、これは最初の投稿に関して適切なようです。 :)

0
WGRM