今後の変更によってダメージが発生しないようにbashスクリプトを強化するにはどうすればよいですか?

今後の変更によってダメージが発生しないようにbashスクリプトを強化するにはどうすればよいですか?

そのため、ホームフォルダ(より正確には書き込み権限を持つすべてのファイル)を削除しました。どうしたの?

build="build"
...
rm -rf "${build}/"*
...
<do other things with $build>

Bashスクリプトで$build不要になった宣言とすべての目的を削除します。しかし、rmBashは喜んでrm -rf /*それを拡張します。

愚かな気分になってバックアップをインストールし、失われた操作を再開しました。恥ずかしさを避けようとします。

今気になります。そのようなエラーが発生しない、または少なくとも発生する可能性を減らすためにbashスクリプトを書く技術は何ですか?たとえば、私が書いた場合

FileUtils.rm_rf("#{build}/*")

Rubyスクリプトでは、インタプリタがbuild宣言されていないことについて文句を言うので、言語は私を保護します。

牧場に加えて、私がbashで考慮したものrm(関連する質問に対する多くの答えで述べたように問題がないわけではありません):

  1. rm -rf "./${build}/"*
    これで私の現在の作業(Gitリポジトリ)は破壊されますが、それ以外には何もありません。
  2. rm対応するバリアント/パラメータ化は、現在のディレクトリ外で動作しているときに対話が必要です。 (何も見つかりません。) 同様の効果です。

そうですか?それとも、この意味で「堅牢な」bashスクリプトを書く別の方法はありますか?

答え1

set -u

または

set -o nounset

これにより、現在のシェルは設定されていない変数の拡張をエラーとして扱います。

$ unset build
$ set -u
$ rm -rf "$build"/*
bash: build: unbound variable

set -uそしてset -o nounsetPOSIX シェルオプション

一つ会う価値があるいいえしかし、エラーが発生します。

これを行うには、次を使用します。

$ rm -rf "${build:?Error, variable is empty or unset}"/*
bash: build: Error, variable is empty or unset

空または設定されていない場合、拡張は${variable:?word}その値に拡張されます。variable空であるか設定されていない場合、word標準エラーに表示され、シェルは拡張をエラーとして扱います(コマンドは実行されず、非対話式シェルで実行すると終了します)。省略すると、:以下のように設定されていない値に対してのみエラーが発生しますset -u

${variable:?word}POSIX パラメータ拡張

set -e(またはset -o errexit)も適用されない限り、これらのいずれも対話型シェルは終了しません。${variable:?word}変数が空であるか設定されていない場合、スクリプトは終了します。set -uと一緒に使用すると、スクリプトは終了しますset -e


2番目の質問は。rm現在、ディレクトリ外の作業を制限する方法はありません。

GNUの実装にはマウントされたファイルシステムを再帰的に削除するのを防ぐオプションがありますが、rm実際には引数をチェックする関数で呼び出しをラップすることなく--one-file-systemできるだけ近づくことができると思います。rm


注:拡張が文字列の一部として発生しない限り、${build}これはまったく同じです。ここで次の文字は、inなどの変数名の有効な文字です。$build"${build}x"

答え2

test一般的な検証には/を使用することをお勧めします。[ ]

次のようにスクリプトを書くと安全です。

build="build"
...
[ -n "${build}" ] || exit 1
rm -rf "${build}/"*
...

小切手は[ -n "${build}" ]"${build}"長さがゼロ以外の文字列

||Bashの論理OR演算子です。最初のコマンドが失敗すると、他のコマンドが実行されます。

これを行うと、${build}既に空または未定義の状態になります。スクリプトが終了します(戻りコード1、これは一般的なエラーです)。

これは常に偽物なので、${build}すべての用途を削除しても保護されます。[ -n "" ]


test[ ]/を使用すると、より意味のあるチェックを使用できるという利点があります。

たとえば、

[ -f FILE ] True if FILE exists and is a regular file.
[ -d FILE ] True if FILE exists and is a directory.
[ -O FILE ] True if FILE exists and is owned by the effective user ID.

答え3

あなたの特定のケースでは、過去にファイル/ディレクトリを移動するために「削除」を修正しました(/ tmpがあなたのディレクトリと同じパーティションにあると仮定)。

# mktemp -d is also a good, reliable choice
trashdir=/tmp/.trash-$USER/trash-`date`
mkdir -p "$trashdir"
...
mv "${build}"/* "$trashdir"
...

$trashdir後ろには、ディレクトリ構造を参照し、各ファイルのディスクブロックをすぐに解放するのに時間を費やすことなく、ソースから同じパーティションの宛先ディレクトリ構造に最上位のファイル/ディレクトリ参照を移動します。これにより、システムがアクティブな使用中にクリーンアップが速くなり、再起動が少し遅くなります(再起動時に/ tmpがクリーンアップされます)。

または、大量のディスク容量を消費するプロセス(ビルドなど)の場合、/tmp/.trash-$USERのcronエントリを定期的にクリーンアップすると、/ tmpがいっぱいになるのを防ぐことができます。ディレクトリが/ tmpとは異なるパーティションにある場合は、パーティションに/ tmpなどのディレクトリを作成し、cronでそれらをクリーンアップするようにできます。

しかし、何よりも最善の点は、何らかの方法で変数を混乱させた場合、クリーンアップが発生する前に内容を復元できることです。

答え4

私はいつも一行でBashスクリプトを起動しようとします#!/bin/bash -ue

-e「最初の失敗」を意味する金利間違い";

-u「最初の使用に失敗したことを意味します。あなたn変数宣言」。

この素晴らしい記事で詳細を探す非公式のBash厳格モードを使用する(デバッグが好きでない限り)。著者はまた使用をお勧めしますset -o pipefail; IFS=$'\n\t'が、私の目的ではこれは少し過剰です。

関連情報