次の形式のファイルのリストがあります。
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
どちらの場合も、先頭を削除してファイル削除操作を実行しますecho
。echo rm
答え2
一般的に、bashスクリプトとシェルスクリプトはこれにひどいツールです。これを行うには、awkやPerlのようなものを使用する方が良いでしょう。たとえば、
perl -00 -F'\n' -ae 'shift @F; push @del, @F; END {unlink @del}' list.txt
-00
Perlに短絡モードで入力を読み取るように指示しますlist.txt
(段落は1つ以上の空行で区切られます).この-a
オプションを使用すると、Perlは自動的に各入力段落を名前付き配列に分割します@F
(-F'\n'
このオプションは改行を区切り文字として使用します)。その後、スクリプトは@ Fの最初の要素(with shift
)を破棄し、@ Fの残りの部分を@del
withという別の配列に追加しますpush
。すべての入力を読み取り、処理した後、ブロックが実行され、配列内END
の@del
すべてのファイル名を削除(リンク解除)します。
必要に応じて、「nnnファイルを削除しますか(y / n)?」などの確認質問を簡単に追加したり、削除する前に削除したいすべてのファイルを一覧表示したりできます。または、削除されたファイルの数を印刷してみてください。
何らかの理由でbashで削除を実行したい場合は、@del
代わりにENDブロック(ファイル名間の区切り文字としてNULを使用)に配列を印刷することができ、unlink @del
bashスクリプトは出力を次のようにパイプすることができます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は空のフィールドを保持しますが、それも役に立ちません。)foo
bar
foo<newline>bar
foo::bar
IFS=:
ファイルを 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
出力が正しい場合は削除してください。