可能なすべてのファイル名を正しく処理することは可能ですか? [閉鎖]

可能なすべてのファイル名を正しく処理することは可能ですか? [閉鎖]

Linuxでは、ファイル名には「スラッシュ」と「ヌル文字」の2文字しか禁止されていません。したがって、すべてのスクリプト言語で特別な意味を持つすべての文字はエスケープする必要がありますが、すべてのエスケープシーケンスはファイル名にも許可されます。悪いことに、bashのいくつかのエスケープ方法は特定の文字だけをエスケープするので、他の多くの文字をエスケープするには、いくつかのエスケープ方法を一緒に使用する必要がありますが、互いに干渉します。ある意味では、一部のコマンドは目的を達成するために特定の文字を使用し、他のコマンドは別の文字を使用するため、ファイルに対するすべての簡単な操作に対してファイル名を別々にエスケープする必要があります。説得力のあるファイル名を安全に区切るためにヌル文字しか使用できませんが、ほとんどのコマンドはヌル文字とは連携しません。悪いことに、基本的にLinuxのすべてはファイルです。したがって、これは迷惑であるだけでなく、セキュリティと信頼性の問題でもあります。 Linuxのほとんどはスクリプトベースなので、非常に欠陥がたくさんあります!

それで、私がどこで間違っているのか教えてください...すべての可能なファイル名を正しく処理することは可能ですか?

言う。もともと私はそう思った。

  1. 特定のパスの下のファイルとフォルダのリスト

  2. 指定された基準(年齢、ファイルモード、またはサイズ)に一致するコンテンツのリストを取得します。

  3. 一致するファイルとフォルダをカテゴリ(映画など)に移動します。テストの複雑さのため、1つのコマンドでこれを行うことは不可能(または実用的)であるため、異なるコマンド間でファイル名を渡す必要があります。 Bashワイルドカードはファイル名にスペースがあるため、最初に削除されます。ワイルドカードは常にスペースを含むファイル名をリストの2つの要素に分割します。それから「検索」を試してみました。これは良いですが、はるかに遅くて使いにくいです。

ファイル名にどの文字があるかわからないため、ファイル名をエスケープするために特殊文字を使用することはできません。いくつかのテストを経た後、どのキャラクターが登場するのかは時間の問題であることがわかりました。

私は次のようにフィルタを定義してみました。 audio_ext=(*.mp3 *.wav *.ogg *.mid *.mod *.stm *.s3m *.it *.wma *.669 *.ac3) ワイルドカードを使用すると権限が奪われるため、この方法で複数の用途でフィルタを定義することはできません。だからワイルドカードと歴史を無効にしましたset -fH。ワイルドカードがない場合は、手動で拡張する必要があります。

while IFS= read -r -d $'\0'; do list+=("$REPLY") done < <( find . -maxdepth 1 -mindepth 1 ${params[@]} -print0 2>/dev/null )

paramsこのような配列はどこにありますか"-iname" "*.mp3" "-o" "-iname" "*.wav"?これは、ファイル名に "(" が含まれるまで機能します。) ルックアップは、誤った使用に関するエラーを返します。

正直なところ、私は最近まで15年間この作業にバッチスクリプトを使用してきました。書き込み時間は午後1~2時程度です。欠点とファイル名の問題がありますが、!通常は動作します。私はほぼ2ヶ月間bashで書こうとしました。醜くて複雑でバグでいっぱいで、うまく動作しないようです。

答え1

単純な。グローブを使用して目的のファイルを選択し、ファイル名を含む変数を参照します。

shopt -s nullglob
for file in ./*.txt; do
    do_something_with "$file"
done

それはすべてです。

詳細は:


アップデート:ワイルドカードはいいえあなたが見る単語分割効果を引き起こします。変数を参照できません。

次の方法で条件に合ったファイル情報を取得できます。stat

read size mtime < <(stat -c "%s %Y" "$file")
[[ $size -gt 1000 ]] && echo "too big"
[[ $mtime -lt $(date -d yesterday +%s) ]] && echo "too old"

更新2:多くの特殊文字を含むファイル名を作成するには、さまざまな引用メカニズムを混在させる必要がありますが、ファイルに対してすべての操作を実行することは依然として可能です。

$ filename='~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`'"'"$' \a\t\n\r\f'".txt"
#          ^^ single quoted part ^^^^^^^^^^^^^^^^   
#                             double quoted part ^^^
#                                ANSI-C quoted part ^^^^^^^^^^^^^^

$ echo "$filename"
~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`'   

.txt

$ printf "%q\n" "$filename"
$'~ASDFzxcv!@#$%^&*()_+[]\\{}|;:",.<>?`\' \a\t\n\r\f.txt'

$ date > "$filename"

$ cat "$filename"
Thu Apr 12 15:14:29 EDT 2018

$ ls -lt
total 3836
-rw-rw-r-- 1 jackman jackman      29 Apr 12 15:14 ~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`' ?????.txt

$ ls -lt --show-control-chars
total 3836
-rw-rw-r-- 1 jackman jackman      29 Apr 12 15:14 ~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`'     

.txt

出力がls端末以外のもの(ファイルやパイプなど)にリダイレクトされる場合は、デフォルトで--show-control-charsこのスタイルを使用します。を実行すると、これを確認できますls -lt | cat。  lsたとえば、他の表示オプションがあります。--quoting-style=WORD

答え2

\0ファイル名には、ヌル文字()とスラッシュ(パス区切り文字)を除く任意の文字を使用できます。変数にはすべてのデータを保持できます(ほとんどのシェルではNULL文字を除く)。正しく引用すると、ファイル名を変数に安全に保存し、ユーティリティと一緒に使用できます。

あなたのポイントに関して:

ファイルセット(通常のファイルまたはディレクトリ)を繰り返すには、次の単純なシェルループを使用できます。

for name in ./*; do
    # some code that uses "$name"
done

特定の基準を使用して特定のファイルを選択するときにファイルを繰り返す方がfind良い選択肢です。たとえば、現在のディレクトリ(またはそれ以下)から数日より古い(少なくとも数日前に変更された)すべてのN一般ファイルを選択するには、次の手順を実行します。N

find . -type f -mtime +N

同様に-size、サイズに基づいてファイルを選択し、-nameファイル名をワイルドカードパターンと一致させるために使用されます。

*.movたとえば、先週変更されたファイル名と一致する一般ファイルを選択するには、次のようにします。

find . -type f -name '*.mov' -mtime -7

だから実際にはする$HOME/Moviesこれらのファイルをディレクトリに移動するなど、いくつかの作業を行います。

find . -type f -name '*.mov' -mtime -7 -exec mv {} "$HOME/Movies" ';'

{}呼び出し時にファイルのパス名を変更しますmv。パス名に対するシェルのトークン化またはファイル名拡張が呼び出されないため、引用する必要はありません{}(もしそうなら、何も変更されません)。find

これに対するさらなる改善は、ターゲットディレクトリ内のファイル名の競合を検出することです。これを行うには、コマンドラインから複数のファイル名を取得する短いヘルパースクリプトを使用します。

destdir="$HOME/Movies"
for name do
    if [ -f "$destdir/${name##*/}" ]; then
        printf "%s already exists in %s, not overwriting it!\n" "${name##*/}" "$destdir" >&2
    else
        mv "$name" "$destdir"
    fi
done

またはショートカット形式で:

destdir="$HOME/Movies"
for name do
    [ -f "$destdir/${name##*/}" ] && printf "skipping %s\n" "$name" >&2 && continue
    mv "$name" "$destdir"
done

次のコマンドに以下を挿入してくださいfind

find . -type f -name '*.mov' -mtime -7 -exec sh -c '
    destdir="$HOME/Movies"
    for name do
        [ -f "$destdir/${name##*/}" ] && printf "skipping %s\n" "$name" >&2 && continue
        mv "$name" "$destdir"
    done' sh {} +

このプロセス中に、シェルは現在処理中のパス名またはファイル名に対してトークン化またはファイル名の一致を実行することを許可しません。

より多くの情報を知りたい場合:

関連情報