我有一个格式的文件列表
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" 我想跳过第一个文件,删除其余文件,然后当两个换行符出现时,将计数重置为 0 并为下一批再次执行此操作。当我将 IFS 设置为单个换行符时,我得到了意外的结果 - 图像文件不再正确解析。当您从脚本中删除 IFS 时,脚本不会检测到两个换行符。帮助!并提前致谢。
代码:
#!/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
如果你确实想使用 shell 循环,你可以这样做:
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 和一般的 shell 脚本对于这项工作来说是一个很糟糕的工具。你最好用 awk 或 perl 之类的东西来做这件事。例如:
perl -00 -F'\n' -ae 'shift @F; push @del, @F; END {unlink @del}' list.txt
-00
告诉 Perl 以段落模式读取其输入list.txt
(段落由一个或多个空行分隔)。该-a
选项使 perl 自动将每个输入段落拆分为一个名为 的数组@F
(由于该-F'\n'
选项,使用换行符作为分隔符)。然后,该脚本丢弃 @F 的第一个元素(with shift
),并将 @F 的其余部分添加到另一个名为@del
with 的数组中push
。读取并处理所有输入后,END
将执行该块,这将删除(取消链接)@del
数组中的所有文件名。
如果您愿意,可以很容易地添加一个确认问题 - 例如“删除 nnn 文件(是/否)?”,也许在删除它们之前列出所有要删除的文件。或者只是打印已删除文件的计数。
如果您出于某种原因想在 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 块打印两个数组,以及将保留或删除的文件的计数。
答案3
设置IFS=$"\n\n"
与设置相同IFS='\n\n'
,将其设置为反斜杠,字母n,反斜杠,字母n。要解释反斜杠转义符,您需要使用$'...'
,而不是$"..."
,后者用于国际化(iirc)。
无论如何,它在这里对您没有帮助,因为分词将连续的空白分隔符视为一个,因此foo<newline><newline>bar
分为foo
和bar
,与 相同foo<newline>bar
。 (对于非空白分隔符则不是这种情况,例如foo::bar
withIFS=:
确实保留空字段,但这对您也没有帮助。)
逐行读取文件可能更容易。这甚至会将单个空行视为分隔符,因为这要容易得多,而且我看不到您会如何处理空行:
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
如果输出正确则删除。