如何在 bash 中从新到旧解压文件直到成功

How to untar files from youngest to oldest one until success in bash

我在一个目录中得到了 tar 个文件(备份)的列表,需要取消 tar 它们从最年轻到最旧的尝试直到成功。

问题是 tar 文件有时会损坏,需要取消 tar 旧文件。

这就是我找到最小的一个的方法,但不确定如何将它放入 while 中并找到下一个。

tar -xf `find /config/config_*.tar -maxdepth 1 -type f -size +1k -print0 | xargs -0 ls -hat | head -n 1`

提前致谢

您已经有一个可以工作的 find | xargs ls 可以为您提供排序的文件列表,最新的在前。 (正如@sjsam 指出的那样,假设 none 的文件名包含换行符。)您可以使用 process-substitution feature of bash to put that list in a file, one entry per line, so that it can be read by a while ... read loop。然后一次备份成功就可以退出循环

    # vvvvvvvvvvvvvvvvvvvvvv get the next filename into $tarname
while IFS= read -r tarname
do  
    tar -xf "$tarname" && break
                    #  ^^^^^^^^ done with the loop if tar succeeds
done <   <(find /config/config_*.tar -maxdepth 1 -type f -size +1k -print0 | xargs -0 ls -hat)  
   # ^   ^^^^---- the process substitution
   # |
   # +--------- the redirection from the process subsitution

请注意,您确实需要 < <(...),而不仅仅是 <(...)

(顺便说一句,你需要 ls -hat,还是 ls -t 也可以?)


编辑! 又名 拍摄 3

我受到@sjsam 评论的启发,写了ls0,一个ls 替代品,可以提供以NULL 结尾的输出。如果发现任何错误,请检查并提交问题!它在 Cygwin 上使用 Perl 5.22 进行了测试,但应该适用于大多数 Perl 5。它是单个文件 — 要安装,请将 ls0 放在您的路径中,然后 chmod ugo+x ls0.

使用ls0,上面的技术可以从容地处理奇怪命名的文件:

while IFS= read -r -d '' tarname
do  
    tar -xf "$tarname" && break
done <   <(find /config/config_*.tar -maxdepth 1 -type f -size +1k -print0 | ls0 -t)

(如@sjsam 所述,-d '' 在功能上等同于 -d $'[=26=]'


Take 2 page sjsam links 建议使用 perl 而不是 ls 来对文件名进行排序。这样您就可以处理其中包含换行符或其他奇怪字符的文件名。

这是该技术的一个示例(不是 OP 问题的答案)。我已经用包含换行符的文件名对其进行了测试。以下代码打印当前目录中的 *.tar 个文件,最新的在前。此示例在每个文件名前后打印斜线,以便您可以看到带换行符的文件名得到了正确处理。在 cygwin、bash 4.3.46、perl 5.22.2.

上测试
perl -e 'opendir(my $dirh, "."); print(join "[=12=]", sort { -M $a <=> -M $b } grep /.tar$/, readdir($dirh))' \ 
| while IFS= read -r -d $'[=12=]' name
do 
    printf "/%s/\n" "$name"
done

因此,将所有这些放在一起以获得 (1) 回答 OP 的问题并且 (2) 对具有奇怪内容的文件名具有鲁棒性的代码:

perl -e 'opendir(my $dirh, "."); print(join "[=13=]", sort { -M $a <=> -M $b } grep /.tar$/, readdir($dirh))' \ 
| while read -r -d $'[=13=]' tarname
do
    tar -xf "$tarname" && break
done

或者,使用进程替换,

while IFS= read -r -d $'[=14=]' tarname
do
    tar -xf "$tarname" && break
done < <(perl -e 'opendir(my $dirh, "."); print(join "[=14=]", sort { -M $a <=> -M $b } grep /.tar$/, readdir($dirh))')

简单吧? ;)

来源:

  • 正在按排序顺序读取目录:this answer
  • 读取 bash 中以 null 结尾的数据:this blog post(bash 的 read 可以与以 null 结尾的字符串一起使用)
  • 在 perl 中过滤列表:this answer

下面的脚本可以做到

find /path/to/tarball/folder -type f -name "*.tar" -printf "%A@--%p[=10=]" |
sort -rnz | while read -r -d '' stampedfile 
do
  tar -xzf "${stampedfile#*--}" >/dev/null 2>&1
  [ $? -eq 0 ] && echo "Newest Working Tar: ${stampedfile#*--}" && exit
done

我们做了什么

  • 使用 find 列出空终止的 tarball 文件,这些文件前面加上自纪元以来的最后一次访问时间(以秒为单位)和我们稍后用作分隔符的 --
  • 使用修改时间降序排列文件名。这是 -- 分隔符派上用场的地方
  • 使用 while 循环解析空终止的排序文件
  • 从文件名中删除 time-- 并尝试解压它
  • 如果尝试成功,则退出