Bash関数がエラー出力を生成するのはなぜですか?

Bash関数がエラー出力を生成するのはなぜですか?

cronを使用して定期的に実行されるスクリプトがあります。このスクリプトが失敗した場合は、電子メール通知を受け取りたいと思います。実行され、出力が生成されるたびに通知を受けたくありません。

だからスクリプトを使用しています。クロニクcron内でジョブを実行するとは、出力ではなくエラー出力のみを送信することを意味します。

しかし、スクリプトには、ディレクトリが空であるかどうかを検出するbash関数があります。

# function to check if directory is empty outputs 0 if empty, or
# some positive integer if it does not
is_dir_empty () 
{
  ls -A "$1" | wc -c
}

この関数は、次のようにif文で使用されます。

if [ "$(is_dir_empty "${local_backup_location}/${folder_to_backup_basename}")" -eq 0 ]; then
    # save space by removing diffs older than 1 month
    rdiff-backup --remove-older-than 1M --force ${local_backup_location}/${folder_to_backup_basename} || logger -t ${logger_name}  "No existing data backup"
fi

時にはcronicからこのような電子メールを受け取ります。

Cronic detected failure or error output for the command:
/usr/local/sbin/backup_uploads

RESULT CODE: 0

ERROR OUTPUT:
ls -A /mnt/storage-2/data_upload_backup/upload

STANDARD OUTPUT:
<snip>

TRACE-ERROR OUTPUT:
+ folder_to_backup=/mnt/storage-1/sftp_data/sftpuser1/upload
+ backup_server=ed-mh-x86001.mydomain.tld
+ backup_folder=data_upload_backup
+ remote_backup_storage_root=/mnt/storage-2
+ local_backup_storage_root=/mnt/storage-2
+ logger_name=backup_uploads
+ error_report_dir=/root/rdiff-backup-errors
+ [email protected]
+ problems=0
+ lockfilename=/root/.backup_uploads_lockfile.pid
+ test -e /root/.backup_uploads_lockfile.pid
+ echo
+ basename /mnt/storage-1/sftp_data/sftpuser1/upload
+ folder_to_backup_basename=upload
+ hostname
+ remote_server_backup_folder=/mnt/storage-2/data_upload_backup_ed-mh-pi01
+ mkdir -p /root/rdiff-backup-errors
+ logger -t backup_uploads Starting backup for folder /mnt/storage-1/sftp_data/sftpuser1/upload
+ test -d /mnt/storage-2
+ local_backup_location=/mnt/storage-2/data_upload_backup
+ mkdir -p /mnt/storage-2/data_upload_backup
+ logger -t backup_uploads Backing up to /mnt/storage-2/data_upload_backup
+ mkdir -p /root/rdiff-backup-errors
+ test -d /mnt/storage-2/data_upload_backup/upload
+ is_dir_empty /mnt/storage-2/data_upload_backup/upload
+ + wc -c
ls -A /mnt/storage-2/data_upload_backup/upload
+ [ 26 -eq 0 ]
+ logger -t backup_uploads Starting backup for /mnt/storage-1/sftp_data/sftpuser1/upload
+ hostname
+ rdiff-backup --ssh-no-compression /mnt/storage-1/sftp_data/sftpuser1/upload /mnt/storage-2/data_upload_backup/upload/
+ testssh [email protected]
+ remote_backup_machine_accessible=0
+ [ 0 -eq 0 ]
+ [email protected]:/mnt/storage-2/data_upload_backup_ed-mh-pi01
+ logger -t backup_uploads copying local backup up to [email protected]:/mnt/storage-2/data_upload_backup_ed-mh-pi01
+ rsync -aP --delete-after /mnt/storage-2/data_upload_backup/ [email protected]:/mnt/storage-2/data_upload_backup_ed-mh-pi01
+ rm -f /root/.backup_uploads_lockfile.pid
+ logger -t backup_uploads Lock file /root/.backup_uploads_lockfile.pid deleted, end of script

スクリプトを実行するたびにこのメッセージは表示されず、時々表示されます。したがって、ディレクトリが実際には空であるか、それと似ている可能性があります。

だから私の質問は、このコマンドでエラー出力が表示される理由とそれを回避する方法です(少なくともエラーが発生したくない明白なコンテキストでは)。

この質問は、私が直接尋ねた他の質問と非常によく似ています。ここしかし、私はそれが異なると確信しています!

答え1

これは、コマンドのエラー出力をキャプチャする方法と出力を切り離すcronic方法によって異なります。xtrace

+1つ以上で始まる行を見つけます。

これはおおよその経験的な方法です。一部のエラーメッセージは、+これで始まる場合はトレース出力として分類されます。 xtrace行がエラーメッセージ行、または並列に開始された他のコマンドの他のxtrace行とインタリーブされると(あなたの場合のように)、行の先頭にない可能性があり、+xtrace出力として分類されます。スクリプトが別のスクリプトを使用すると、PS4正常に動作しません。

スクリプトの場合、bashxtrace出力からエラー出力を分離するには、以下を使用する必要がありますBASH_XTRACEFD

BASH_XTRACEFD=7 bash -x /path/to/your-script > out 2> err 7> trace

答え2

リンクされた質問の質問に似ています。出力でこの部分を確認してください。

+ + wc -c
ls -A /mnt/storage-2/data_upload_backup/upload

cronicが行を取得するために出力を解析するとき、先行する行がstraceない行を見て、それを一般的なエラー出力の一部と見なします。+

出力は混乱しています。次の2つのコマンドの一般的なxtrace出力のみを表示する必要があります。

+ ls -A /mnt/storage-2/data_upload_backup/upload
+ wc -c

ここで起こらなければならないことは、Dashがstderrに送信するために呼び出す前に出力ライン全体をバッファリングすることを気にせず、write()むしろ1つずつ実行することです。これを行う2つのシェルプロセスがパイプラインの各部分に1つずつあり、同時に実行されるため、出力が混乱する可能性があります。ロードされたシステムで大きな問題なしにこれを繰り返すことができます。

+この結果を得るには、最初に書いてからコマンドを書くだけで十分ですが、もっと悪いようです。また、次のような出力を取得します。

+ is_dir_empty .
+ + lswc -A -c .

つまり、単一の単語でもインターリーブされます。

個人がstraceを使って書いているのがわかります。

$ strace -etrace=write -f dash -xc 'echo aa bb cc'
write(2, "+ ", 2+ )                       = 2
write(2, "echo", 4echo)                     = 4
write(2, " aa", 3 aa)                      = 3
write(2, " bb", 3 bb)                      = 3
write(2, " cc", 3 cc)                      = 3
write(2, "\n", 1
)
...

この問題をシェルで修正する以外に、他の方法は思い出されません。

Bashにはこの問題はないようですが、代わりに試してみてください。

答え3

これは関係があるかもしれませんし、関係がないかもしれませんが、ls出力を解析しない;ここにもありません。

シンプル

number_of_items () { echo $#; }
is_empty() {
 shopt -s dotglob
 shopt -s nullglob
 value=$(number_of_items $1/*)
 [[ "${value}" -eq 0 ]]
}

できるようにそうします

if is_empty "${local_backup_location}/${folder_to_backup_basename}" ; then

あなたが本当に欲しいものcontains_accessible_things

number_of_items () { echo $#; }
contains_accessible_things() {
 shopt -s dotglob
 shopt -s nullglob
 # check for directory and readability 
 [[ -d "$1" && -r "$1" && ( "$(number_of_items $1/*)" -gt 0 ) ]]
}
if contains_accessible_things "${local_backup_location}/${folder_to_backup_basename}" ; then
  do_stuff
fi

関連情報