質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
bash

bash(Bourne-again-Shell)は sh(Bourne Shell)のインプリメンテーションに様々な機能が追加されたシェルです。LinuxやMac OS XではBashはデフォルトで導入されています。

Q&A

解決済

2回答

2344閲覧

シェル(bash)の実行行を確実に変数に取得したい

gaccha

総合スコア6

bash

bash(Bourne-again-Shell)は sh(Bourne Shell)のインプリメンテーションに様々な機能が追加されたシェルです。LinuxやMac OS XではBashはデフォルトで導入されています。

0グッド

0クリップ

投稿2020/02/22 06:13

編集2020/02/22 06:16

前提・実現したいこと

trap DEBUG内で、シェルの各実行行を変数${BASH_COMMAND}で取得しようとしているのですが、サブシェル内のコマンド内容が${BASH_COMMAND}に入らず困っています。
※実際にはサブシェル実行前のコマンド内容が${BASH_COMMAND}にセットされています。

今作成しようとしているのは共通関数なので、出来れば実装ファイル内に何度も読み込ませるようなことはしたくないです。
※例えば解決策としてサブシェル内でもう一度trap DEBUGするなどはしたくないです。

■実行結果
引数に存在しないディレクトリを指定して実行します。("test"など)

<想定>
error line:XX sorce_code:"cd $A_dir" exited with status 1.

<実状>
error line:XX sorce_code:"echo "no sach directory."" exited with status 1.

該当のソースコード

#!/bin/sh set -T trap catch ERR trap finally EXIT function catch() { errcode=$? echo "error line:$A_LineNo sorce_code:\"$A_SrcCode\" exited with status $errcode." exit 8 } function finally() { echo FINALLY } trap 'test_exec $LINENO "${BASH_COMMAND}"' DEBUG function test_exec() { set +T A_LineNo=$1 A_SrcCode=$2 } echo pre A_dir=$1 if [ ! -d $A_dir ] ; then echo "no sach directory." fi (cd $A_dir) echo post exit 0

試したこと

「set -T」を使用して取得出来るか試したのですが、どうも取れないようです。

補足情報(FW/ツールのバージョンなど)

■OS情報
[root@web_server cmn_shell]# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.4 (Maipo)

■bashバージョン
[root@web_server cmn_shell]# bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

gaccha

2020/02/22 08:19

ご回答ありがとうございます。 ・まずシェバングについては、ご指摘の通り正しくは 「#!/bin/bash」で記載した方が良さそうなので修正致しました。 ・「set -o functrace」につきましては略式が「set -T」と認識しており、試しにご指摘頂いた「set -o functrace」で指定し直しましたが、事象変わらずでした。。。 と思いましたが、デバッグ実行したら取得されていたようなので、上記シェルのバグですね。。。 「function test_exec()」関数内で「set +T」をしたまま戻してないので、それが原因でした。。。
guest

回答2

0

独自メッセージを出すだけなら、これでどうでしょうか?

Bash

1#!/bin/bash 2set -ET 3 4trap 'echo "Error line:$LINENO sorce_code:\"$BASH_COMMAND\" exited with status $?.";kill '$$ ERR 5 6trap 'echo FINALLY' EXIT 7 8echo pre 9A_dir=$1 10 11(cd $A_dir) 12 13echo post 14 15exit 0

子シェルでtrap ERRが動いた後、親シェルでもtrap ERRが動いてしまうようなので、exit 非ゼロじゃなくて親シェルをkillしています。そのため、最後に「シェルがkillされた」旨のメッセージが出ます。

投稿2020/02/22 16:41

otn

総合スコア85901

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

自己解決

上記修正の上、色々と検証した結果、変数のスコープが問題のようでした。

・サブシェル実行時には以下の通りとなります
A_SrcCode='cd $A_dir'

・しかし、親に処理が戻るとサブシェルが設定した値は破棄される為、前回実行したコマンドの結果が残っています。
A_SrcCode='echo 'no sach directory.'

仕方がないので一旦ファイルに変数の値を吐き出してゴニョゴニョすることとしました。
より良い方法があればご教示頂けると幸いです。

otn様より頂いた記法がシンプルかつ確実でしたので、使用させて頂きました。

個人的な思いとしては
・エラーならとりあえずcatchして独自エラーを吐き、処理をメインに戻す。
・継続させたい場合もあるので、終了処理はメイン処理の中で判断して記載する。

~~・なるべくメインシェルには共通関数導入による記法制限をかけたくない。
というところがありまして、出来れば「set -E」はつけたくない思いがありました。
~~
~~エラーでも継続させたい場合は「||」で繋げて独自エラー関数を呼び出すような仕組みにしてみました。(これが制約となってしまいましたが・・・)
~~

2020/02/24 19:56 : 記載修正、シェル再アップ
otn様とやりとりを重ねた結果、当初と実装手法がことなりましたので最終版をアップしました。
とても助かりました!ありがとうございました!

・「set -E」でサブシェル内の処理までERRトラップが行われる。
※「set -e」とは異なり、エラー終了はされない。
・上記注釈記載により「||」による実装制約は解消。ただし、コマンドエラーの結果はcatch()で処理するようにしているため、実装処理では一律$RCでステータスコードを取る制約とする。
・サブシェルでERRトラップした場合、親シェルでもう一度ERRトラップが走ってしまい、余計なメッセージが出力されてしまう為、catch()内で「サブシェル→親シェル」に戻ったかを判定する処理を実装。(サブシェルでセットした変数の値は親シェルに引き継がれない為、ファイルで管理)
・パイプラインのエラーに対応する為、「set -o pipefail」を追加。
・シェルを共通関数「cmn_func.sh」と実装シェル「main_shell.sh」に分離。

■ cmn_func.sh

#!/bin/bash set -E -o pipefail #### 初期化処理 RC=0 echo 0 > SUBSHELL #### 処理開始メッセージ Msg="============ Script(${BASH_SOURCE[1]}) be in started. =>=>=>=>" echo -e ${Msg} #### 各種トラップおよび独自メッセージ定義 trap 'catch "${BASH_COMMAND}"' ERR function catch(){ RC=(${PIPESTATUS[@]}) ## サブシェル対応(現時点が親シェルの場合、サブシェルから復帰しているか確認) if [ $BASH_SUBSHELL -eq 0 -a `cat SUBSHELL` -ne 0 ] ; then echo $BASH_SUBSHELL > SUBSHELL return 0 fi A_SrcCode=$1 L_LineNo=$BASH_LINENO L_ExecCMD=`eval "echo $A_SrcCode"` Msg= Msg+=">>>> Script Error Occurred! ($0:$$): \n" Msg+="SorceFile: ${BASH_SOURCE[1]} \n" Msg+="LineNo(${FUNCNAME[1]}): $L_LineNo \n" if [ ${#RC[@]} -eq 1 ] ; then Msg+="Sorce_Code: $A_SrcCode \n" Msg+="Execute_CMD: $L_ExecCMD \n" else Msg+="Sorce_Code: $A_SrcCode ( Pipeline be in used. Please check one lines. ) \n" Msg+="Execute_CMD: $L_ExecCMD ( Pipeline be in used. Please check one lines. ) \n" fi Msg+="Sub_Shell: $BASH_SUBSHELL \n" Msg+="Exited with status: ${RC[@]}." echo -e ${Msg} echo ## サブシェル対応(判定処理の為に、現在のサブシェル状態をファイルへ書き込み) if [ $BASH_SUBSHELL -ne 0 ] ; then echo $BASH_SUBSHELL > SUBSHELL fi } #### 終了処理 trap finary EXIT function finary(){ Msg= Msg="=>=>=>=>=>=> Script(${BASH_SOURCE[1]}) finished. =============" echo -e ${Msg} rm SUBSHELL }

■ main_shell.sh

#!/bin/bssh #### 共通関数読み込み . ./cmn_func.sh #### メイン ## 事前処理 echo -e "pre \n" A_dir=$1 ## サブシェルコマンド (ls -d $A_dir ; cd $A_dir) ## 判定を入れて例外処理や終了処理を入れる(サブシェル) if [ $RC -ne 0 ] ; then echo -e "サブシェル版例外処理\n" # exit 10 fi ## 親シェルコマンド ls -d $A_dir ; cd $A_dir #ls -d ${A_dir} | xargs -I {} echo "Current Directory is {}." # パイプラインエラーテスト X | 0 #echo "test" | xargs ls -d # パイプラインエラーテスト 0 | X #ls -d ${A_dir} | xargs cd # パイプラインエラーテスト X | X ## 判定を入れて例外処理や終了処理を入れる(親シェル) total=0 for val in ${RC[@]} ; do total=$((val + total)) done RC=$total if [ $RC -ne 0 ] ; then echo -e "親シェル版例外処理\n" # exit 10 fi echo -e "post \n" exit 0

投稿2020/02/22 10:40

編集2020/02/24 10:59
gaccha

総合スコア6

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

otn

2020/02/23 08:53

要件がよくわかりません。 ・全コマンドに対して、エラーなら独自メッセージを出したい ・一部のコマンドについては、その後、スクリプトを終了したい ・残りのコマンドについては、独自メッセージ出力後も、そのコマンドの次から処理を続ける ということでしょうか? それとも、 ・一部のコマンドについては、エラー時に独自メッセージを出して、その後、スクリプトを終了したい ・残りのコマンドについては、エラー時にも独自メッセージを出さず、その次のコマンドから実行を続ける(標準の動作) でしょうか? そもそも、独自メッセージを出す意味が不明です。実際にはメッセージを出すのでなく、コマンドや行番号を見て何かの処理が入るのでしょうか?
gaccha

2020/02/23 11:55

otn様 この度は考え方までお付き合い頂きありがとうございます。 要件についてお答え致します。 (といってもまだまだしっかり固まっておらず、共通関数を作るにあたってどんな機能があるといいかなぁ~と探りながら作成し始めた次第です。) ・全コマンドに対して、エラーなら独自メッセージを出したい。(最終的にはログ出力を想定) 今の所、要件として答えられるのはこれだけですね。 その際に「set -E」をつけてしまうと、独自メッセージは出力してくれますがメインシェルが終了してしまうので、エラーでも継続して処理を進めたい場合があると融通が効かなくなってしまうのでは・・・と思った次第です。 で、独自メッセージを出す意味については・・・「”sh -x”でデバッグ出来るからいいでしょ」とかそういうことをご指摘されているのかと受け取りましたが、一応ログに出力させようかなと思っている次第です。 思いとしては、メインシェルを作成する人には実装したい処理だけ記載してもらって、共通関数が自動でロギングするようなものを目指しています。
otn

2020/02/23 12:08

ログに書くのですか。なるほど。 「set -E を付けるとメインシェルが終了」というのがわかりません。 終了したくないのなら、kill しなければいいだけでは?
gaccha

2020/02/23 12:21

「set -E」をつけると、エラーが発生した場合に処理が終了するものだと思っていましたが・・・認識誤りですかね・・・。ちょっと検証してみます! 一旦現時点の作成済みソースを再アップしてみました。
otn

2020/02/23 12:36

それは、set -e です。
gaccha

2020/02/23 12:54

大文字はスコープが変わるだけだと思ってました・・・。 検証してみましたが確かに終了しませんでしたね。 シェルを分けたせいなのか、サブシェルコマンドを実行したときに変な動きをしだしました。。。 まだまだ解決済みにするのは尚早でしたね。。。
otn

2020/02/23 13:12

ERRが、サブシェル内のトラップでもひっかかり、親に戻ってからも引っかかり、で二重に出ますが、 ログに書くなら、読むときに余分な出力を削除すればいいかと。
gaccha

2020/02/23 13:36

catch()内部で「サブシェル→親に戻ったかどうかの判定処理」を入れて、余分なログ出力がされないように対応しようと考えています。 プロセスkillについては不要となったので削除いたします。 長々とお付き合いくださりありがとうございましたmm
gaccha

2020/02/24 10:32

サブシェル対応を実施しました。 まだまだ検証によりボロが出る可能性は否定出来ませんが、現状ではこれで確定としておきます。 判定手法ですが、サブシェルで設定した変数の値が親シェルに引き継がれないので、やはりファイルを使用してサブシェル判定を行っています。 また、pipelineに対しても「set -o pipefale」が無いとパイプの最後のステータスでしかエラー判定されなかったので、対応しました。 一旦確定として、最終盤をアップし直しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問