GNU/Linux >> Linux の 問題 >  >> Linux

例でBashエラー処理を学ぶ

この記事では、エラー状態を処理するためのいくつかのトリックを紹介します。エラー処理(予期しないものを処理するための反応的な方法)のカテゴリに厳密に分類されないものもあれば、エラーが発生する前に回避するためのテクニックもあります。

ケーススタディ:複数のホストからハードウェアレポートをダウンロードしてデータベースに挿入する簡単なスクリプト。

cronがあるとします Linuxシステムのそれぞれでジョブを実行し、それぞれからハードウェア情報を収集するためのスクリプトがあります。

#!/bin/bash
# Script to collect the status of lshw output from home servers
# Dependencies:
# * LSHW: http://ezix.org/project/wiki/HardwareLiSter
# * JQ: http://stedolan.github.io/jq/
#
# On each machine you can run something like this from cron (Don't know CRON, no worries: https://crontab-generator.org/)
# 0 0 * * * /usr/sbin/lshw -json -quiet > /var/log/lshw-dump.json
# Author: Jose Vicente Nunez
#
declare -a servers=(
dmaf5
)

DATADIR="$HOME/Documents/lshw-dump"

/usr/bin/mkdir -p -v "$DATADIR"
for server in ${servers[*]}; do
    echo "Visiting: $server"
    /usr/bin/scp -o logLevel=Error ${server}:/var/log/lshw-dump.json ${DATADIR}/lshw-$server-dump.json &
done
wait
for lshw in $(/usr/bin/find $DATADIR -type f -name 'lshw-*-dump.json'); do
    /usr/bin/jq '.["product","vendor", "configuration"]' $lshw
done

すべてがうまくいけば、システムが10個を超えないため、ファイルを並行して収集します。それらすべてに同時にsshを実行してから、それぞれのハードウェアの詳細を表示する余裕があります。

Visiting: dmaf5
lshw-dump.json                                                                                         100%   54KB 136.9MB/s   00:00    
"DMAF5 (Default string)"
"BESSTAR TECH LIMITED"
{
  "boot": "normal",
  "chassis": "desktop",
  "family": "Default string",
  "sku": "Default string",
  "uuid": "00020003-0004-0005-0006-000700080009"
}

物事がうまくいかなかった理由のいくつかの可能性があります:

  • サーバーがダウンしたため、レポートは実行されませんでした
  • ファイルを保存する必要のあるディレクトリを作成できませんでした
  • スクリプトを実行するために必要なツールがありません
  • リモートマシンがクラッシュしたため、レポートを収集できません
  • 1つ以上のレポートが破損しています

スクリプトの現在のバージョンには問題があります。エラーの有無にかかわらず、最初から最後まで実行されます。

./collect_data_from_servers.sh 
Visiting: macmini2
Visiting: mac-pro-1-1
Visiting: dmaf5
lshw-dump.json                                                                                         100%   54KB  48.8MB/s   00:00    
scp: /var/log/lshw-dump.json: No such file or directory
scp: /var/log/lshw-dump.json: No such file or directory
parse error: Expected separator between values at line 3, column 9

次に、スクリプトをより堅牢にし、場合によっては障害から回復するためのいくつかのことを示します。

核の選択肢:激しく失敗し、速く失敗する

エラーを処理する適切な方法は、戻りコードを使用して、プログラムが正常に終了したかどうかを確認することです。当たり前のように聞こえますが、リターンコード、bash $?に格納されている整数 または$! 変数であり、より広い意味を持つ場合があります。 bashのmanページに次のように表示されます:

シェルの目的のために、ゼロの終了ステータスで終了するコマンドが成功しました。
ゼロの終了ステータスは成功を示します。
ゼロ以外の終了ステータスは失敗を示します。コマンドが
致命的な信号Nで終了すると、bashは128+Nの値を
終了ステータスとして使用します。

いつものように、呼び出しているスクリプトのマニュアルページを常に読んで、各スクリプトの規則を確認する必要があります。 JavaやPythonなどの言語でプログラミングしたことがある場合は、それらの例外、さまざまな意味、およびそれらすべてが同じように処理されるわけではない方法に精通している可能性があります。

set -o errexitを追加した場合 スクリプトに対して、その時点から、コード!=0 のコマンドが存在する場合、実行は中止されます。 。しかし、 errexit if内で関数を実行する場合は使用されません 条件なので、その例外を覚える代わりに、明示的なエラー処理を行います。

スクリプトのバージョン2を見てください。少し良いです:

1 #!/bin/bash
2 # Script to collect the status of lshw output from home servers
3 # Dependencies:
4 # * LSHW: http://ezix.org/project/wiki/HardwareLiSter
5 # * JQ: http://stedolan.github.io/jq/
6 #
7 # On each machine you can run something like this from cron (Don't know CRON, no worries: https://crontab-generator.org/        ) 
8 # 0 0 * * * /usr/sbin/lshw -json -quiet > /var/log/lshw-dump.json
9   Author: Jose Vicente Nunez
10 #
11 set -o errtrace # Enable the err trap, code will get called when an error is detected
12 trap "echo ERROR: There was an error in ${FUNCNAME-main context}, details to follow" ERR
13 declare -a servers=(
14 macmini2
15 mac-pro-1-1
16 dmaf5
17 )
18  
19 DATADIR="$HOME/Documents/lshw-dump"
20 if [ ! -d "$DATADIR" ]; then 
21    /usr/bin/mkdir -p -v "$DATADIR"|| "FATAL: Failed to create $DATADIR" && exit 100
22 fi 
23 declare -A server_pid
24 for server in ${servers[*]}; do
25    echo "Visiting: $server"
26    /usr/bin/scp -o logLevel=Error ${server}:/var/log/lshw-dump.json ${DATADIR}/lshw-$server-dump.json &
27   server_pid[$server]=$! # Save the PID of the scp  of a given server for later
28 done
29 # Iterate through all the servers and:
30 # Wait for the return code of each
31 # Check the exit code from each scp
32 for server in ${!server_pid[*]}; do
33    wait ${server_pid[$server]}
34    test $? -ne 0 && echo "ERROR: Copy from $server had problems, will not continue" && exit 100
35 done
36 for lshw in $(/usr/bin/find $DATADIR -type f -name 'lshw-*-dump.json'); do
37    /usr/bin/jq '.["product","vendor", "configuration"]' $lshw
38 done

変更点は次のとおりです:

  • 11行目と12行目では、エラートレースを有効にし、「トラップ」を追加して、エラーが発生し、前方に乱気流があることをユーザーに通知します。代わりに、ここでスクリプトを強制終了することをお勧めします。それが最善ではない理由を説明します。
  • 20行目、ディレクトリが存在しない場合は、21行目で作成してみてください。ディレクトリの作成に失敗した場合は、エラーで終了します。
  • 27行目で、各バックグラウンドジョブを実行した後、PIDをキャプチャし、それをマシンに関連付けます(1:1の関係)。
  • 33〜35行目で、 scpを待ちます タスクを終了し、リターンコードを取得し、エラーの場合は中止します。
  • 37行目で、ファイルが解析できることを確認します。解析できない場合は、エラーで終了します。

では、エラー処理はどのようになりますか?

Visiting: macmini2
Visiting: mac-pro-1-1
Visiting: dmaf5
lshw-dump.json                                                                                         100%   54KB 146.1MB/s   00:00    
scp: /var/log/lshw-dump.json: No such file or directory
ERROR: There was an error in main context, details to follow
ERROR: Copy from mac-pro-1-1 had problems, will not continue
scp: /var/log/lshw-dump.json: No such file or directory

ご覧のとおり、このバージョンはエラーの検出に優れていますが、非常に寛容ではありません。また、すべてのエラーを検出するわけではありませんか?

行き詰まり、目覚ましがあればいいのに

scp の場合を除いて、コードの見栄えは良くなります。 サーバーがビジー状態で応答できないか、状態が悪いために、(ファイルをコピーしようとしているときに)サーバーでスタックする可能性があります。

もう1つの例は、NFSを介して $ HOMEのディレクトリにアクセスしようとすることです。 NFSサーバーからマウントされます:

/usr/bin/find $HOME -type f -name '*.csv' -print -fprint /tmp/report.txt

そして、数時間後、NFSマウントポイントが古く、スクリプトがスタックしていることに気付きます。

タイムアウトが解決策です。そして、GNUタイムアウトが助けになります:

/usr/bin/timeout --kill-after 20.0s 10.0s /usr/bin/find $HOME -type f -name '*.csv' -print -fprint /tmp/report.txt

ここでは、プロセスが開始されてから10.0秒後に、プロセスを定期的に強制終了(TERMシグナル)しようとします。 20.0秒経っても実行されている場合は、KILLシグナルを送信します( kill -9 )。疑わしい場合は、システムでサポートされている信号を確認してください( kill -l <​​/ code> 、たとえば)。

これが私のダイアログから明らかでない場合は、スクリプトを見てより明確にしてください。

/usr/bin/time /usr/bin/timeout --kill-after=10.0s 20.0s /usr/bin/sleep 60s
real    0m20.003s
user    0m0.000s
sys     0m0.003s

元のスクリプトに戻ってさらにいくつかのオプションを追加すると、バージョン3があります:

 1 #!/bin/bash
  2 # Script to collect the status of lshw output from home servers
  3 # Dependencies:
  4 # * Open SSH: http://www.openssh.com/portable.html
  5 # * LSHW: http://ezix.org/project/wiki/HardwareLiSter
  6 # * JQ: http://stedolan.github.io/jq/
  7 # * timeout: https://www.gnu.org/software/coreutils/
  8 #
  9 # On each machine you can run something like this from cron (Don't know CRON, no worries: https://crontab-generator.org/)
 10 # 0 0 * * * /usr/sbin/lshw -json -quiet > /var/log/lshw-dump.json
 11 # Author: Jose Vicente Nunez
 12 #
 13 set -o errtrace # Enable the err trap, code will get called when an error is detected
 14 trap "echo ERROR: There was an error in ${FUNCNAME-main context}, details to follow" ERR
 15 
 16 declare -a dependencies=(/usr/bin/timeout /usr/bin/ssh /usr/bin/jq)
 17 for dependency in ${dependencies[@]}; do
 18     if [ ! -x $dependency ]; then
 19         echo "ERROR: Missing $dependency"
 20         exit 100
 21     fi
 22 done
 23 
 24 declare -a servers=(
 25 macmini2
 26 mac-pro-1-1
 27 dmaf5
 28 )
 29 
 30 function remote_copy {
 31     local server=$1
 32     echo "Visiting: $server"
 33     /usr/bin/timeout --kill-after 25.0s 20.0s \
 34         /usr/bin/scp \
 35             -o BatchMode=yes \
 36             -o logLevel=Error \
 37             -o ConnectTimeout=5 \
 38             -o ConnectionAttempts=3 \
 39             ${server}:/var/log/lshw-dump.json ${DATADIR}/lshw-$server-dump.json
 40     return $?
 41 }
 42 
 43 DATADIR="$HOME/Documents/lshw-dump"
 44 if [ ! -d "$DATADIR" ]; then
 45     /usr/bin/mkdir -p -v "$DATADIR"|| "FATAL: Failed to create $DATADIR" && exit 100
 46 fi
 47 declare -A server_pid
 48 for server in ${servers[*]}; do
 49     remote_copy $server &
 50     server_pid[$server]=$! # Save the PID of the scp  of a given server for later
 51 done
 52 # Iterate through all the servers and:
 53 # Wait for the return code of each
 54 # Check the exit code from each scp
 55 for server in ${!server_pid[*]}; do
 56     wait ${server_pid[$server]}
 57     test $? -ne 0 && echo "ERROR: Copy from $server had problems, will not continue" && exit 100
 58 done
 59 for lshw in $(/usr/bin/find $DATADIR -type f -name 'lshw-*-dump.json'); do
 60     /usr/bin/jq '.["product","vendor", "configuration"]' $lshw
 61 done

変更点は何ですか?:

  • 16〜22行目で、必要な依存関係ツールがすべて存在するかどうかを確認します。実行できない場合は、「ヒューストン、問題があります。」
  • remote_copyを作成しました 関数。タイムアウトを使用して、 scpを確認します。 45.0秒以内に終了します—33行目。
  • TCPのデフォルトの代わりに5秒の接続タイムアウトを追加しました—37行目。
  • scpに再試行を追加しました 38行目—それぞれの間に1秒間待機する3回の試行。

エラーが発生したときに再試行する他の方法があります。

世界の終わりを待っています-いつどのように再試行するか

scpに再試行が追加されていることに気づきました 指図。ただし、失敗した接続に対してのみ再試行します。コピーの途中でコマンドが失敗した場合はどうなりますか?

問題から回復する機会がほとんどないために、失敗したい場合があります。たとえば、ハードウェアの修正が必要なシステム、または単に劣化モードにフェールバックできるシステム。つまり、更新されたデータがなくてもシステムの作業を続行できます。そのような場合、永遠に待つことは意味がありませんが、特定の時間だけ待つことになります。

remote_copyの変更点は次のとおりです 、この簡潔さを保つために(バージョン4):

#!/bin/bash
# Omitted code for clarity...
declare REMOTE_FILE="/var/log/lshw-dump.json"
declare MAX_RETRIES=3

# Blah blah blah...

function remote_copy {
    local server=$1
    local retries=$2
    local now=1
    status=0
    while [ $now -le $retries ]; do
        echo "INFO: Trying to copy file from: $server, attempt=$now"
        /usr/bin/timeout --kill-after 25.0s 20.0s \
            /usr/bin/scp \
                -o BatchMode=yes \
                -o logLevel=Error \
                -o ConnectTimeout=5 \
                -o ConnectionAttempts=3 \
                ${server}:$REMOTE_FILE ${DATADIR}/lshw-$server-dump.json
        status=$?
        if [ $status -ne 0 ]; then
            sleep_time=$(((RANDOM % 60)+ 1))
            echo "WARNING: Copy failed for $server:$REMOTE_FILE. Waiting '${sleep_time} seconds' before re-trying..."
            /usr/bin/sleep ${sleep_time}s
        else
            break # All good, no point on waiting...
        fi
        ((now=now+1))
    done
    return $status
}

DATADIR="$HOME/Documents/lshw-dump"
if [ ! -d "$DATADIR" ]; then
    /usr/bin/mkdir -p -v "$DATADIR"|| "FATAL: Failed to create $DATADIR" && exit 100
fi
declare -A server_pid
for server in ${servers[*]}; do
    remote_copy $server $MAX_RETRIES &
    server_pid[$server]=$! # Save the PID of the scp  of a given server for later
done

# Iterate through all the servers and:
# Wait for the return code of each
# Check the exit code from each scp
for server in ${!server_pid[*]}; do
    wait ${server_pid[$server]}
    test $? -ne 0 && echo "ERROR: Copy from $server had problems, will not continue" && exit 100
done

# Blah blah blah, process the files you just copied...

今はどうですか?この実行では、1つのシステムがダウンしている(mac-pro-1-1)と、ファイルがない1つのシステム(macmini2)があります。サーバーからのコピーdmaf5 すぐに機能しますが、他の2つについては、終了する前に1〜60秒のランダムな時間で再試行します。

INFO: Trying to copy file from: macmini2, attempt=1
INFO: Trying to copy file from: mac-pro-1-1, attempt=1
INFO: Trying to copy file from: dmaf5, attempt=1
scp: /var/log/lshw-dump.json: No such file or directory
ERROR: There was an error in main context, details to follow
WARNING: Copy failed for macmini2:/var/log/lshw-dump.json. Waiting '60 seconds' before re-trying...
ssh: connect to host mac-pro-1-1 port 22: No route to host
ERROR: There was an error in main context, details to follow
WARNING: Copy failed for mac-pro-1-1:/var/log/lshw-dump.json. Waiting '32 seconds' before re-trying...
INFO: Trying to copy file from: mac-pro-1-1, attempt=2
ssh: connect to host mac-pro-1-1 port 22: No route to host
ERROR: There was an error in main context, details to follow
WARNING: Copy failed for mac-pro-1-1:/var/log/lshw-dump.json. Waiting '18 seconds' before re-trying...
INFO: Trying to copy file from: macmini2, attempt=2
scp: /var/log/lshw-dump.json: No such file or directory
ERROR: There was an error in main context, details to follow
WARNING: Copy failed for macmini2:/var/log/lshw-dump.json. Waiting '3 seconds' before re-trying...
INFO: Trying to copy file from: macmini2, attempt=3
scp: /var/log/lshw-dump.json: No such file or directory
ERROR: There was an error in main context, details to follow
WARNING: Copy failed for macmini2:/var/log/lshw-dump.json. Waiting '6 seconds' before re-trying...
INFO: Trying to copy file from: mac-pro-1-1, attempt=3
ssh: connect to host mac-pro-1-1 port 22: No route to host
ERROR: There was an error in main context, details to follow
WARNING: Copy failed for mac-pro-1-1:/var/log/lshw-dump.json. Waiting '47 seconds' before re-trying...
ERROR: There was an error in main context, details to follow
ERROR: Copy from mac-pro-1-1 had problems, will not continue

失敗した場合、これをもう一度やり直す必要がありますか?チェックポイントの使用

リモートコピーがこのスクリプト全体の中で最もコストのかかる操作であり、おそらく cron を使用して、このスクリプトを再実行する意思がある、または実行できると仮定します。 または、1日2回手作業で行うことで、1つ以上のシステムがダウンした場合にファイルを確実に取得できるようにします。

その日は、マシンごとに成功した処理操作のみを記録する小さな「ステータスキャッシュ」を作成できます。システムがそこにある場合は、その日をもう一度確認する必要はありません。

Ansibleなどの一部のプログラムは、同様のことを行い、障害が発生した後、限られた数のマシンでプレイブックを再試行できるようにします(-limit @ / home / user / site.retry

スクリプトの新しいバージョン(バージョン5)には、コピーのステータスを記録するコードが含まれています(15〜33行目):

15 declare SCRIPT_NAME=$(/usr/bin/basename $BASH_SOURCE)|| exit 100
16 declare YYYYMMDD=$(/usr/bin/date +%Y%m%d)|| exit 100
17 declare CACHE_DIR="/tmp/$SCRIPT_NAME/$YYYYMMDD"
18 # Logic to clean up the cache dir on daily basis is not shown here
19 if [ ! -d "$CACHE_DIR" ]; then
20   /usr/bin/mkdir -p -v "$CACHE_DIR"|| exit 100
21 fi
22 trap "/bin/rm -rf $CACHE_DIR" INT KILL
23
24 function check_previous_run {
25  local machine=$1
26  test -f $CACHE_DIR/$machine && return 0|| return 1
27 }
28
29 function mark_previous_run {
30    machine=$1
31    /usr/bin/touch $CACHE_DIR/$machine
32    return $?
33 }

22行目の罠に気づきましたか?スクリプトが中断(強制終了)された場合は、キャッシュ全体が無効になっていることを確認したいと思います。

次に、この新しいヘルパーロジックを remote_copyに追加します 関数(52〜81行目):

52 function remote_copy {
53    local server=$1
54    check_previous_run $server
55    test $? -eq 0 && echo "INFO: $1 ran successfully before. Not doing again" && return 0
56    local retries=$2
57    local now=1
58    status=0
59    while [ $now -le $retries ]; do
60        echo "INFO: Trying to copy file from: $server, attempt=$now"
61        /usr/bin/timeout --kill-after 25.0s 20.0s \
62            /usr/bin/scp \
63                -o BatchMode=yes \
64                -o logLevel=Error \
65                -o ConnectTimeout=5 \
66               -o ConnectionAttempts=3 \
67                ${server}:$REMOTE_FILE ${DATADIR}/lshw-$server-dump.json
68        status=$?
69        if [ $status -ne 0 ]; then
70            sleep_time=$(((RANDOM % 60)+ 1))
71            echo "WARNING: Copy failed for $server:$REMOTE_FILE. Waiting '${sleep_time} seconds' before re-trying..."
72            /usr/bin/sleep ${sleep_time}s
73        else
74            break # All good, no point on waiting...
75        fi
76        ((now=now+1))
77    done
78    test $status -eq 0 && mark_previous_run $server
79    test $? -ne 0 && status=1
80    return $status
81 }

初めて実行すると、キャッシュディレクトリの新しい新しいメッセージが出力されます。

./collect_data_from_servers.v5.sh
/usr/bin/mkdir: created directory '/tmp/collect_data_from_servers.v5.sh'
/usr/bin/mkdir: created directory '/tmp/collect_data_from_servers.v5.sh/20210612'
ERROR: There was an error in main context, details to follow
INFO: Trying to copy file from: macmini2, attempt=1
ERROR: There was an error in main context, details to follow

もう一度実行すると、スクリプトは dma5f を認識します 行ってもいいです。コピーを再試行する必要はありません:

./collect_data_from_servers.v5.sh
INFO: dmaf5 ran successfully before. Not doing again
ERROR: There was an error in main context, details to follow
INFO: Trying to copy file from: macmini2, attempt=1
ERROR: There was an error in main context, details to follow
INFO: Trying to copy file from: mac-pro-1-1, attempt=1

再訪すべきでないマシンがもっとあるときに、これがどのようにスピードアップするか想像してみてください。

クラムを残す:何を記録するか、どのように記録するか、そして詳細な出力

あなたが私のようなら、何かがうまくいかないときに相関するコンテキストが少し好きです。 echo スクリプトのステートメントは素晴らしいですが、タイムスタンプを追加できるとしたらどうでしょう。

loggerを使用する場合 、出力を journalctlに保存できます 後で確認するために(他のツールとの集約も)。最良の部分は、 journalctlの力を示すことです すぐに。

したがって、 echoを実行するだけでなく 、 loggerへの呼び出しを追加することもできます このように、「 message」と呼ばれる新しいbash関数を使用します ’:

SCRIPT_NAME=$(/usr/bin/basename $BASH_SOURCE)|| exit 100
FULL_PATH=$(/usr/bin/realpath ${BASH_SOURCE[0]})|| exit 100
set -o errtrace # Enable the err trap, code will get called when an error is detected
trap "echo ERROR: There was an error in ${FUNCNAME[0]-main context}, details to follow" ERR
declare CACHE_DIR="/tmp/$SCRIPT_NAME/$YYYYMMDD"

function message {
    message="$1"
    func_name="${2-unknown}"
    priority=6
    if [ -z "$2" ]; then
        echo "INFO:" $message
    else
        echo "ERROR:" $message
        priority=0
    fi
    /usr/bin/logger --journald<<EOF
MESSAGE_ID=$SCRIPT_NAME
MESSAGE=$message
PRIORITY=$priority
CODE_FILE=$FULL_PATH
CODE_FUNC=$func_name
EOF
}

優先度、メッセージを生成したスクリプトなど、メッセージの一部として個別のフィールドを保存できることがわかります。

では、これはどのように役立ちますか?ええと、あなたは get 午後1時26分から午後1時27分までのメッセージ、エラーのみ( priority =0 )およびスクリプト( collect_data_from_servers.v6.sh のみ) )このように、JSON形式で出力します:

journalctl --since 13:26 --until 13:27 --output json-pretty PRIORITY=0 MESSAGE_ID=collect_data_from_servers.v6.sh
{
        "_BOOT_ID" : "dfcda9a1a1cd406ebd88a339bec96fb6",
        "_AUDIT_LOGINUID" : "1000",
        "SYSLOG_IDENTIFIER" : "logger",
        "PRIORITY" : "0",
        "_TRANSPORT" : "journal",
        "_SELINUX_CONTEXT" : "unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023",
        "__REALTIME_TIMESTAMP" : "1623518797641880",
        "_AUDIT_SESSION" : "3",
        "_GID" : "1000",
        "MESSAGE_ID" : "collect_data_from_servers.v6.sh",
        "MESSAGE" : "Copy failed for macmini2:/var/log/lshw-dump.json. Waiting '45 seconds' before re-trying...",
        "_CAP_EFFECTIVE" : "0",
        "CODE_FUNC" : "remote_copy",
        "_MACHINE_ID" : "60d7a3f69b674aaebb600c0e82e01d05",
        "_COMM" : "logger",
        "CODE_FILE" : "/home/josevnz/BashError/collect_data_from_servers.v6.sh",
        "_PID" : "41832",
        "__MONOTONIC_TIMESTAMP" : "25928272252",
        "_HOSTNAME" : "dmaf5",
        "_SOURCE_REALTIME_TIMESTAMP" : "1623518797641843",
        "__CURSOR" : "s=97bb6295795a4560ad6fdedd8143df97;i=1f826;b=dfcda9a1a1cd406ebd88a339bec96fb6;m=60972097c;t=5c494ed383898;x=921c71966b8943e3",
        "_UID" : "1000"
}

これは構造化されたデータであるため、他のログコレクターがすべてのマシンを調べて、スクリプトログを集約すると、データだけでなく情報も取得できます。

スクリプトのバージョン6全体を見ることができます。

データを確認するまで、データを置き換えようと熱心にしないでください。


最初から気付いた方は、破損したJSONファイルを何度もコピーしています:

Parse error: Expected separator between values at line 4, column 11
ERROR parsing '/home/josevnz/Documents/lshw-dump/lshw-dmaf5-dump.json'

それを防ぐのは簡単です。ファイルを一時的な場所にコピーします。ファイルが破損している場合は、以前のバージョンを置き換えようとしないでください(検査のために、不良なバージョンを残してください。スクリプトのバージョン7の99〜107行目):

function remote_copy {
    local server=$1
    check_previous_run $server
    test $? -eq 0 && message "$1 ran successfully before. Not doing again" && return 0
    local retries=$2
    local now=1
    status=0
    while [ $now -le $retries ]; do
        message "Trying to copy file from: $server, attempt=$now"
        /usr/bin/timeout --kill-after 25.0s 20.0s \
            /usr/bin/scp \
                -o BatchMode=yes \
                -o logLevel=Error \
                -o ConnectTimeout=5 \
                -o ConnectionAttempts=3 \
                ${server}:$REMOTE_FILE ${DATADIR}/lshw-$server-dump.json.$$
        status=$?
        if [ $status -ne 0 ]; then
            sleep_time=$(((RANDOM % 60)+ 1))
            message "Copy failed for $server:$REMOTE_FILE. Waiting '${sleep_time} seconds' before re-trying..." ${FUNCNAME[0]}
            /usr/bin/sleep ${sleep_time}s
        else
            break # All good, no point on waiting...
        fi
        ((now=now+1))
    done
    if [ $status -eq 0 ]; then
        /usr/bin/jq '.' ${DATADIR}/lshw-$server-dump.json.$$ > /dev/null 2>&1
        status=$?
        if [ $status -eq 0 ]; then
            /usr/bin/mv -v -f ${DATADIR}/lshw-$server-dump.json.$$ ${DATADIR}/lshw-$server-dump.json && mark_previous_run $server
            test $? -ne 0 && status=1
        else
            message "${DATADIR}/lshw-$server-dump.json.$$ Is corrupted. Leaving for inspection..." ${FUNCNAME[0]}
        fi
    fi
    return $status
}

タスクに適したツールを選択し、最初の行からコードを準備します

エラー処理の非常に重要な側面の1つは、適切なコーディングです。コードに悪いロジックがある場合、エラー処理の量がそれを改善することはありません。これを短くbashに関連させるために、いくつかのヒントを以下に示します。

スクリプトを実行する前に、常にエラー構文を確認する必要があります:

bash -n $my_bash_script.sh

真剣に。他のテストを実行するのと同じくらい自動である必要があります。

bashのマニュアルページを読んで、次のような必知のオプションをよく理解してください。

set -xv
my_complicated_instruction1
my_complicated_instruction2
my_complicated_instruction3
set +xv

ShellCheckを使用してbashスクリプトをチェックします

スクリプトが大きくなり始めると、単純な問題を見逃しがちです。 ShellCheckは、間違いを犯さないようにするツールの1つです。

shellcheck collect_data_from_servers.v7.sh

In collect_data_from_servers.v7.sh line 15:
for dependency in ${dependencies[@]}; do
                  ^----------------^ SC2068: Double quote array expansions to avoid re-splitting elements.


In collect_data_from_servers.v7.sh line 16:
    if [ ! -x $dependency ]; then
              ^---------^ SC2086: Double quote to prevent globbing and word splitting.

Did you mean: 
    if [ ! -x "$dependency" ]; then
...

疑問に思われる場合は、ShellCheckを通過した後のスクリプトの最終バージョンがここにあります。きしむようなきれい。

バックグラウンドのscpプロセスで何かに気づきました

スクリプトを強制終了すると、分岐したプロセスがいくつか残ることに気付いたと思います。これは良くありません。これが、AnsibleやParallelなどのツールを使用して複数のホストでこのタイプのタスクを処理し、フレームワークに適切なクリーンアップを行わせることを好む理由の1つです。もちろん、この状況を処理するためにコードを追加することもできます。

このbashスクリプトは、フォーク爆弾を作成する可能性があります。同時に生成するプロセスの数を制御することはできません。これは、実際の実稼働環境では大きな問題です。また、同時に実行できるsshセッションの数には制限があります(帯域幅を消費することは言うまでもありません)。繰り返しになりますが、この架空の例をbashで記述して、エラーをより適切に処理するためにプログラムを常に改善する方法を示しました。

要約しましょう

[今すぐダウンロード:システム管理者によるBashスクリプトのガイド。 ]

1.コマンドのリターンコードを確認する必要があります。これは、一時的な状態が改善するまで再試行するか、スクリプト全体を短絡するかを決定することを意味する場合があります。
2。一時的な状態と言えば、最初から始める必要はありません。成功したタスクのステータスを保存して、その時点から再試行できます。
3。 Bashの「トラップ」はあなたの友達です。クリーンアップとエラー処理に使用します。
4。任意のソースからデータをダウンロードするときは、データが破損していると想定してください。整合性チェックを行うまでは、適切なデータセットを新しいデータで上書きしないでください。
5。 journalctlおよびカスタムフィールドを利用します。問題を探す高度な検索を実行し、そのデータをログアグリゲーターに送信することもできます。
6。バックグラウンドタスク(サブシェルを含む)のステータスを確認できます。 PIDを保存して待つことを忘れないでください。
7。そして最後に:ShellCheckのようなBashリントヘルパーを使用します。お気に入りのエディター(VIMやPyCharmなど)にインストールできます。 Bashスクリプトで検出されないエラーの数に驚かれることでしょう...

このコンテンツを楽しんだり、拡張したい場合は、[email protected]のチームに連絡してください。


Linux
  1. Bashスクリプトを学ぶための最良のリソース??

  2. Bashシェルのカスタマイズ:コマンドを太字/色で表示しますか?

  3. Bash スクリプト - Hello World の例

  1. Bash でテキスト ファイルをコマンドとして実行する

  2. 予期しないトークン付近の構文エラー ' - bash

  3. Bash スクリプトでエラーを発生させる

  1. タイプセット-aスクリプトにエラーがありますか?

  2. Curl Outfile変数がBashスクリプトで機能しませんか?

  3. Bashスクリプトエラー:整数式が必要ですか?