$(cat list.txt)のfnのヘルプスクリプト/ IFS /

$(cat list.txt)のfnのヘルプスクリプト/ IFS /

次の形式のファイルのリストがあります。

file1.jpg
file2.jpg
file3.jpg
newline
newline
file4.jpg
file5.jpg
file6.jpg
newline
newline
file7.jpg
file8.jpg
file9.jpg
etc

私のbashスクリプトはIFS = $ "\ n"に設定されています。最初のファイルをスキップして残りのファイルを削除してから2つの改行が表示された場合は、カウントをゼロにリセットして次のバッチに対してもう一度やりたいと思います。 IFS を単一の改行に設定すると、予期しない結果が発生します。イメージファイルが正しく解析されなくなりました。スクリプトから IFS を削除すると、スクリプトは 2 つの改行を検出しません。助ける!そしてあらかじめありがとうございます。

パスワード:

#!/bin/bash
#
# MASS DELETE
#
IFS=$"\n\n"
count=0
deleted=0
saved=0
for fn in $(cat list.txt)
do
        length=${#fn}
        ext=${fn:length-3:3}
        echo "**$fn**"

        if [ $ext != "jpg" ]; then
                echo "**Newline**"
                count=0
        else
#               (( ++count ))
#               if [ $count -ge 1 ]; then
#                       echo "Removing $fn..."
#                       #rm $fn
#               else
#                       echo "Saving $fn..."
#               fi
                echo "Do Stuff"
        fi
done

出力(エラー!)

Rigel@Minty-VirtualBox:~/data/comics/2020$ ./mass_del.sh
**12-Dec/miltpriggee-2020-12-10.jpg
12-Dec/miltpriggee-2020-12-11.jpg
12-Dec/miltpriggee-2020-12-30.jpg
12-Dec/miltpriggee-2020-12-17.jpg
12-Dec/miltpriggee-2020-12-21.jpg
12-Dec/miltpriggee-2020-12-28.jpg
12-Dec/miltpriggee-2020-12-01.jpg
12-Dec/miltpriggee-2020-12-03.jpg
12-Dec/miltpriggee-2020-12-12.jpg
12-Dec/miltpriggee-2020-12-15.jpg
12-Dec/miltpriggee-2020-12-20.jpg
12-Dec/miltpriggee-2020-12-25.jpg
12-Dec/miltpriggee-2020-12-07.jpg
12-Dec/miltpriggee-2020-12-27.jpg
12-Dec/miltpriggee-2020-12-29.jpg
12-Dec/miltpriggee-2020-12-16.jpg
12-Dec/miltpriggee-2020-12-26.jpg
12-Dec/miltpriggee-2020-12-02.jpg
12-Dec/miltpriggee-2020-12-18.jpg
12-Dec/miltpriggee-2020-12-06.jpg
12-Dec/miltpriggee-2020-12-19.jpg
12-Dec/miltpriggee-2020-12-13.jpg
12-Dec/miltpriggee-2020-12-04.jpg
12-Dec/miltpriggee-2020-12-31.jpg
12-Dec/miltpriggee-2020-12-22.jpg
12-Dec/miltpriggee-2020-12-24.jpg
12-Dec/miltpriggee-2020-12-14.jpg
12-Dec/miltpriggee-2020-12-05.jpg
12-Dec/miltpriggee-2020-12-09.jpg
12-Dec/miltpriggee-2020-12-08.jpg
12-Dec/miltpriggee-2020-12-23.jpg


12-Dec/kevi**
**Newline**

答え1

awk一重引用符を含むファイル名なしでこれを行うことができます。

awk -v q="'" '
    $0 == "" { count=0; next }
    count++ { print "Delete:", $0; system("echo rm -f -- " q $0 q) }
' list.txt

本当にシェルループを使用したい場合は、次のようにできます。

while IFS= read -r line
do
    # Blank line resets the skip counter
    if [ -z "$line" ]
    then
        count=0

    # Skip the first non-blank line (count==0) then delete others
    elif [ $((count++)) -gt 0 ]
    then
        echo "Delete: $line"
        echo rm -f -- "$line"
    fi
done <list.txt

どちらの場合も、先頭を削除してファイル削除操作を実行しますechoecho rm

答え2

一般的に、bashスクリプトとシェルスクリプトはこれにひどいツールです。これを行うには、awkやPerlのようなものを使用する方が良いでしょう。たとえば、

perl -00 -F'\n' -ae 'shift @F; push @del, @F; END {unlink @del}' list.txt

-00Perlに短絡モードで入力を読み取るように指示しますlist.txt(段落は1つ以上の空行で区切られます).この-aオプションを使用すると、Perlは自動的に各入力段落を名前付き配列に分割します@F-F'\n'このオプションは改行を区切り文字として使用します)。その後、スクリプトは@ Fの最初の要素(with shift)を破棄し、@ Fの残りの部分を@delwithという別の配列に追加しますpush。すべての入力を読み取り、処理した後、ブロックが実行され、配列内END@delすべてのファイル名を削除(リンク解除)します。

必要に応じて、「nnnファイルを削除しますか(y / n)?」などの確認質問を簡単に追加したり、削除する前に削除したいすべてのファイルを一覧表示したりできます。または、削除されたファイルの数を印刷してみてください。

何らかの理由でbashで削除を実行したい場合は、@del代わりにENDブロック(ファイル名間の区切り文字としてNULを使用)に配列を印刷することができ、unlink @delbashスクリプトは出力を次のようにパイプすることができますxargs -0r rm。例えば

perl -00 -F'\n' -ae '
    shift @F; push @del, @F;
    END { print join("\0", @del), "\0" }' list.txt |
  xargs -0r rm

最後に、一度にファイルリンクを解放するのではなく、各段落を読んだ後にファイルリンクを解放する別の短いバージョンがあります。このバージョンは、削除するファイルの累積的なリストを保持することを気にしません。

perl -00 -F'\n' -ae 'shift @F; unlink @F' list.txt

これらのスクリプトの仕組みを示すために何も削除しないいくつかの異なるバージョンがあります。代わりに、実行するジョブのみを印刷します。

$ perl -00 -F'\n' -ae '
  push @keep, shift @F;
  push @del, @F;
  END {
    printf "Keep   %i: %s\n", scalar @keep, join(", ", @keep);
    printf "Delete %i: %s\n", scalar @del, join(", ", @del)
  }' list.txt 
Keep   3: file1.jpg, file4.jpg, file7.jpg
Delete 6: file2.jpg, file3.jpg, file5.jpg, file6.jpg, file8.jpg, file9.jpg

@Fの最初の要素を捨てるのではなく、それを配列に追加します@keep。残りの要素は以前@delと同様に追加されます。 ENDブロックは、保持または削除されるファイルの数とともに2つの配列を印刷します。

答え3

IFS=$"\n\n"設定と同じ設定をIFS='\n\n'バックスラッシュ、文字n、バックスラッシュ、文字nに設定します。バックスラッシュエスケープを解釈するには、国際化(iirc)で使用されるものを$'...'代わりに使用する必要があります。$"..."

とにかく、ここでは役に立ちません。単語分割は連続した空白区切り文字を1つとして扱うため、andはと同じようにfoo<newline><newline>bar扱われます。 (空白以外の区切り文字の場合は該当しません。たとえば、withは空のフィールドを保持しますが、それも役に立ちません。)foobarfoo<newline>barfoo::barIFS=:

ファイルを 1 行ずつ読み込む方が簡単な場合があります。これにより、1行の空行が区切り文字として扱われます。はるかに簡単で、空行をどのように処理するのか分からないからです。

first=1
while IFS= read -r line; do
    # skip leading empty lines and the first non-empty one
    if [ "$first" ]; then
        if ! [ -z "$line" ]; then
            echo "skipping $line"
            first=
        fi
        continue
    fi
    # if line is not empty, remove the file
    # if empty, go back to first line processing
    if [ "$line" ]; then
        echo rm -- "$line"
    else
        first=1
    fi
done

次のようなものを入力してください

file1.jpg
file2.jpg
file3.jpg


file4.jpg
file5.jpg
file6.jpg

file7.jpg
file8.jpg
file9.jpg

あげる

skipping file1.jpg
rm -- file2.jpg
rm -- file3.jpg
skipping file4.jpg
rm -- file5.jpg
rm -- file6.jpg
skipping file7.jpg
rm -- file8.jpg
rm -- file9.jpg

前面にはセキュリティロックがechoあり、rmそれを削除すると実際にファイルが削除されます。


もちろん、Perlでも同じことができますが、rm各ファイルを分岐することなくファイルを削除するので、速度が速くなります。 @roaimaの答えからロジックが削除されました。

$ perl -lne 'chomp; if (/^$/) { $count=0; next; }; 
             next if ($count++ == 0); 
             print "delete: $_"; 
             next; 
             unlink($_) or warn "unlink ($_): $!"' < foo.txt
delete: file2.jpg
delete: file3.jpg
delete: file5.jpg
delete: file6.jpg
delete: file8.jpg
delete: file9.jpg

nextとの間にはprintセキュリティunlinkロックがあり、それを削除すると実際にファイルが削除されます。

答え4

awk+ GNUを使用xargs

$ awk 'NF&&p;{p=NF}' list.txt | xargs -rd'\n' echo rm --
rm -- file2.jpg file3.jpg file5.jpg file6.jpg file8.jpg file9.jpg etc

echo出力が正しい場合は削除してください。

関連情報