コマンドラインでrm *.old.*
を実行すると正しく削除されますが、スクリプトの次の部分で実行すると、すべての*.old.*
ファイルがrmされません。
私のbashスクリプトの何が問題になっています:
for i in ./*; do
if [[ -f $i ]]; then
if [[ $i == *.old.* ]]; then
oldfile=$i
echo "this file is to be removed: $oldfile"
rm $oldfile
exec 2>errorfile
if [ -s $errorfile ]
then
echo "rm failed"
else
echo "removed $oldfile!"
fi
else
echo "file with old extension does not exist"
fi
orig=$i
dest=$i.old
cp $orig $dest
echo "Copied $i"
else
echo "${i} is not a file"
fi
done
あなたが何をしているのか理解している場合(.old
接尾辞を持つファイルを削除し、.old
接尾辞を持つ既存のファイルのコピーを作成する)、代わりにfindを使用できます:
#!/bin/sh
find . -maxdepth 1 -name \*.old -type f -printf "deleting %P\n" -delete
find . -maxdepth 1 -type f -printf "copying %P to %P.old\n" -exec cp '{}' '{}.old' \;
-maxdepth 0
はサブディレクトリの検索コマンドを停止し、-type f
は通常のファイルのみを検索します。 -printf
はメッセージを作成します(%P
は見つかったファイル名です)。 -exec cp
はコピー関数を呼び出し、'{}'
はファイル名です
スクリプトにはさまざまな障害点が考えられます。まず、rm *.old*
は globbing を使用して、一致するすべてのファイルのリストを作成し、空白を含むファイル名を処理できます。ただし、スクリプトはグロブの各結果に変数を割り当て、引用符なしでそれを行います。ファイル名に空白が含まれている場合、これは壊れます。例えば:
$ ls
'file name with spaces.old.txt' file.old.txt
$ rm *.old.* ## works: both files are deleted
$ touch "file.old.txt" "file name with spaces.old.txt"
$ for i in ./*; do oldfile=$i; rm -v $oldfile; done
rm: cannot remove './file': No such file or directory
rm: cannot remove 'name': No such file or directory
rm: cannot remove 'with': No such file or directory
rm: cannot remove 'spaces.old.txt': No such file or directory
removed './file.old.txt'
ご覧のとおり、名前にスペースが含まれているファイルのループは失敗しました。正しく行うには、変数を引用する必要があります。
$ for i in ./*; do oldfile="$i"; rm -v "$oldfile"; done
removed './file name with spaces.old.txt'
removed './file.old.txt'
同じ問題は、スクリプトで$i
を使用するほとんどすべてに当てはまります。 always変数を引用してください .
次に考えられる問題は、*.old.*
が拡張子.old
のファイルと一致することを期待しているように見えることです。そうではありません。 「0個以上の文字」(*
)、次に.
、次に「古い」、別の.
、次に「0個以上の文字」に一致します。つまり、notはfile.old
のようなものに一致しますが、 `file.old.fooのようなものにのみ一致します。
$ ls
file.old file.old.foo
$ for i in *; do if [[ "$i" == *.old.* ]]; then echo $i; fi; done
file.old.foo
したがって、file.old
と一致する敵はありません。いずれにしても、スクリプトは必要以上に複雑です。代わりにこれを試してください:
#!/bin/bash
for i in *; do
if [[ -f "$i" ]]; then
if [[ "$i" == *.old ]]; then
rm -v "$i" || echo "rm failed for $i"
else
echo "$i doesn't have an .old extension"
fi
cp -v "$i" "$i".old
else
echo "$i is not a file"
fi
done
-v
をrm
およびcpwhich does the same thing as what you were doing with your
echo`ステートメントに追加したことに注意してください。
これは完全ではありません。たとえば、file.old
が見つかった場合、これは削除され、スクリプトはそれをコピーしようとし、ファイルが存在しないため失敗します。ただし、スクリプトが実際に何をしようとしているのかを説明していないので、実際に何を達成しようとしているのかを教えてくれない限り、修正することはできません。
I).old
拡張子を持つすべてのファイルを削除し、ii)存在しない既存のファイルに.old
拡張子を追加する場合、本当に必要なのは次のとおりです。
#!/bin/bash
for i in *.old; do
if [[ -f "$i" ]]; then
rm -v "$i" || echo "rm failed for $i"
else
echo "$i is not a file"
fi
done
## All the ,old files have been removed at this point
## copy the rest
for i in *; do
if [[ -f "$i" ]]; then
## the -v makes cp report copied files
cp -v "$i" "$i".old
fi
done
rm $oldfile
が失敗する可能性があるのは、ファイル名にIFS
(スペース、タブ、改行)の文字またはグロブ文字(*
、?
、[]
)。
IFS
の任意の文字が存在する場合、シェルは、変数展開でのグロビング文字パス名展開の存在に基づいて、Word分割を実行します。
たとえば、ファイル名がfoo bar.old.
の場合、変数oldfile
にはfoo bar.old.
が含まれます。
あなたがするとき:
rm $oldfile
シェルは最初、スペース上のoldfile
の展開をfoo
とbar.old.
の2つの単語に分割します。したがって、コマンドは次のようになります。
rm foo bar.old.
これは明らかに予期しない結果につながります。ちなみに、展開にグロブ演算子(*
、?
、[]
)がある場合、パス名の展開も行われます。
目的の結果を得るには、変数を引用符で囲む必要があります。
rm "$oldfile"
これで、Wordの分割やパス名の展開は行われないため、目的の結果が得られるはずです。つまり、目的のファイルが削除されます。いずれかのファイル名が-
で始まる場合、次を実行します。
rm -- "$oldfile"
[[
内で使用するときに変数を引用する必要がない理由を尋ねるかもしれません。その理由は[[
がbash
キーワードであり、変数リテラルを内部で展開リテラルを保持して処理するためです。
今、いくつかのポイント:
rm
コマンドの前にSTDERR(exec 2>errorfile
)をリダイレクトする必要があります。そうしないと、[[ -s errorfile ]]
テストで誤検知が発生します
[ -s $errorfile ]
を使用し、変数拡張$errorfile
を使用しています。これは、errorfile
変数がどこにも定義されていない場合、NULになります。おそらく、あなたは、単に[ -s errorfile ]
を意味し、STDERRリダイレクトに基づいています
変数errorfile
が定義されている場合、[ -s $errorfile ]
を使用しているときに、上記のIFS
およびグロビングのケースで再び詰まります。なぜなら[[
とは異なり、[
はbash
によって内部的に処理されます
スクリプトの後半では、すでに変数を引用せずに、既に削除されたファイルをcp
しようとしています。これは意味がありません。そのチャックをチェックし、ターゲットに基づいて必要な修正を行う必要があります。