web-dev-qa-db-ja.com

rmはコマンドラインでは機能しますが、スクリプトでは機能しません

コマンドラインで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
10
Don

あなたが何をしているのか理解している場合(.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はコピー関数を呼び出し、'{}'はファイル名です

3
Nick Sillito

スクリプトにはさまざまな障害点が考えられます。まず、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個以上の文字」に一致します。つまり、notfile.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

-vrmおよびcpwhich does the same thing as what you were doing with yourecho`ステートメントに追加したことに注意してください。

これは完全ではありません。たとえば、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
13
terdon

rm $oldfileが失敗する可能性があるのは、ファイル名にIFS(スペース、タブ、改行)の文字またはグロブ文字(*?[])。

IFSの任意の文字が存在する場合、シェルは、変数展開でのグロビング文字パス名展開の存在に基づいて、Word分割を実行します。

たとえば、ファイル名がfoo bar.old.の場合、変数oldfileにはfoo bar.old.が含まれます。

あなたがするとき:

rm $oldfile

シェルは最初、スペース上のoldfileの展開をfoobar.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しようとしています。これは意味がありません。そのチャックをチェックし、ターゲットに基づいて必要な修正を行う必要があります。

8
heemayl