帮助脚本 / IFS / for fn in $(cat list.txt)

帮助脚本 / IFS / for fn in $(cat list.txt)

我有一个格式的文件列表

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

在这两种情况下,删除前导echoecho 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 的其余部分添加到另一个名为@delwith 的数组中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分为foobar,与 相同foo<newline>bar。 (对于非空白分隔符则不是这种情况,例如foo::barwithIFS=:确实保留空字段,但这对您也没有帮助。)

逐行读取文件可能更容易。这甚至会将单个空行视为分隔符,因为这要容易得多,而且我看不到您会如何处理空行:

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

前面echorm安全锁,将其删除即可实际删除文件。


当然,您可以在 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如果输出正确则删除。

相关内容