如何循环遍历目录中的前几个文件?

如何循环遍历目录中的前几个文件?

目前我正在使用以下 zsh-snippet 来选择小批量文件以进行进一步处理

for f in $(ls /some/path/*.txt | head -2) ; do
  echo unpacking $f
  ./prepare.sh $f && rm -v $f
done

有没有比$(ls ... | head -2)zsh 更好的替代方案?

我的任务的总体概述。我正在创建一个数据集来训练神经网络。机器学习任务的细节在这里并不重要。数据集创建的任务需要我手动处理大量文件。为此,我将它们复制到一个单独的目录中。然后,我随机选择几个文件(本示例中的输出中的前两个ls),调用一些预处理例程,检查其结果,将其中一些文件移动到正在创建的数据集中,并删除其余文件。清理完毕后,我再次执行上面的命令。

此外,我想提高我的 shell 编程技能并学习新的东西:)

选择这些“第一个”文件的顺序并不重要,因为所有这些文件最终都会被处理。

换句话说,我正在for循环内与 PC 一起工作,并希望它在几次迭代后暂停并等待我。

伪代码。

for f in /some/path/*.txt ; do
  echo unpacking $f
  ./prepare $f
  
  if human wants to review ; then
     human is reviewing then cleans, and PC waits
  fi
done

这种奇怪的过程的原因是,一个“源”.txt文件的预处理会创建几十个其他文件,我需要查看所有这些文件并选择一些适合训练网络的样本(通常是 1-2 个)。

我可以运行for f in /some/path/*.txt ; do ./prepare $f ; done,但这个命令会创建数百个文件,这个数量令人难以承受。

答案1

全局限定符

Glob 限定符可以替代大多数文件的使用lsfind枚举文件。它们是 zsh 的独特功能。

例如,$(ls /some/path/*.txt | head -2)(按字典顺序枚举文件,只保留前两个文件)相当于zsh中的1/some/path/*.txt(N[1,2]) 。限定符N确保在没有匹配项时列表为空,并且限定符将匹配项限制在指定范围内。[from,to]

如果没有N限定符,在默认选项下,如果没有匹配的文件,您的脚本将退出并显示错误消息。

您可以使用oorO限定符来控制文件的顺序。例如,/some/path/*.txt(Nom[1,2])获取两个最新的文件。

1 有一些细微的差别,通常对 zsh 有利。使用ls包含特殊字符(例如空格或换行符或无效字节序列)的文件名往往会出现问题,而 zsh 的内置功能可以在所有文件名上可靠地工作。错误管理在极端情况下是不同的。在这里,由于您忘记了-d选项,如果其中一些文件属于类型,ls您也会遇到问题*.txt目录asls会列出它们的内容。


不过,我不明白获取两个文件如何有助于实现您的总体目标。如果您希望有一种方法来处理所有文件,但允许人员查看前几个文件,您可以显示步骤/继续/中止提示。像这样的东西:

pause=1
for f in /some/path/*.txt ; do
  print -ru2 unpacking $f
  ./prepare $f
  
  if ((pause)); then
    print -ru2 -- "$f output is ready for review."
    c=
    while [[ $c != [anq] ]]; do
      read -k1 "c?Process (N)ext, (A)ll, (Q)uit? " && c=${c:l}
    done
    echo
    case $c in
      a) pause=0;;
      q) break;;
    esac
  fi
done

答案2

您可以在循环中使用计数器for。这应该适用于任何 POSIX 兼容的 shell。

这相当于问题中的第一个代码片段。

i=0
for f in /some/path/*.txt ; do
    if [ "$((i += 1))" -gt 2 ] ; then
        break
    fi
    echo "unpacking $f"
    ./prepare.sh "$f" && rm -v "$f"
done

正如所写吉尔斯的回答zsh具有以更简单的方式实现这一点的功能。有关 glob 限定符的解释,请参阅此答案。

for f in /some/path/*.txt(N[1,2]) ; do
    echo "unpacking $f"
    ./prepare.sh "$f" && rm -v "$f"
done

您也可以等待一些输入,而不是打破循环。

i=0
for f in /some/path/*.txt ; do
    if [ "$((i += 1))" -gt 2 ] ; then
        i=1
        printf "press Enter to continue"
        read dummy
    fi
    printf "unpacking %s\n" "$f"
    ./prepare.sh "$f" && rm -v "$f"
done

注1:在条件分支中,我使用i=1代替,i=0因为脚本已经在本次迭代中处理下一组的第一个文件。

笔记2:我将计数和条件放在循环的开头而不是结尾,因为此时很明显还有另一个文件要处理。这可以避免在最后一个文件之后出现暂停。

注3:我编辑了脚本并添加了引号,$((i += 1))因为斯蒂芬·查泽拉斯在一个中提到评论在大多数 POSIX 兼容 shell 中,将对算术扩展的结果执行通配和分割,这可能会导致不必要的影响,尤其是在IFS包含十进制数字时(我认为这是一种不寻常的情况)。此外,如果该模式与任何文件都不匹配,则将执行循环迭代并将其f设置为文字文件名模式。这可能会导致错误消息或意外结果。为了避免这种情况,请使用 例如 使循环体成为条件if [ -f "$f" ] ...。如果这些特殊情况与预期(交互式)用途无关,则脚本可以保持更简单。

答案3

在 Bash 中,您可以将文件名放入数组中并在数组切片上循环。并循环重复:

#!/bin/bash
shopt -s nullglob
files=(/some/path/*.txt)
for (( i = 0; i < "${#files[@]}"; i += 2)); do
    for f in "${files[@]:i:2}"; do
        printf "processing %s\n" "$f";
    done
    read -p "press enter to continue with the next set (or end)..."
done 

或者进行重组以避免最后可能出现的提示,但代价是失去简单性:

#!/bin/bash
shopt -s nullglob
files=(/some/path/*.txt)
i=0 
while true; do
    for f in "${files[@]:i:2}"; do
        printf "processing %s\n" "$f";
    done
    (( i += 2 ))
    (( i >= "${#files[@]}" )) && break
    read -p "press enter to continue with the next set..."
done

答案4

我建议使用gnu-parallel

  1. 将您的任务放入脚本中
#!/bin/bash
echo unpacking ${1}
/full/path/to/prepare.sh ${1} && rm -v ${1}
  1. 通过以下方式运行它gnu-parallel(可执行文件的实际名称可能会有所不同,具体取决于您的发行版以及程序的安装方式)
parallel --halt soon,success=5 /path/to/script {} ::: *.txt

parallel默认情况下,每个 CPU 运行一个作业,--halt soon,success=5意味着“成功运行 5 个作业后停止处理,但让正在运行的作业完成”。{}替换文件名,并且:::是参数列表的分隔符。

它不会等待您这边的“继续”,但由于您删除了原始文件,因此您可以重新启动该过程并且没有双打。

相关内容