ls 输出的 Foreach 循环

ls 输出的 Foreach 循环

我正在尝试使用 foreach 循环处理目录中的一些文件以获取 ls 输出:

IFS=$'\n'
for i in $(ls -1Atu); do
    echo "$i"
done

第一次我认为它有效,但是当我创建名称类似于*or 的文件时filename*,shell 添加了额外的迭代,因为*解释为通配符。
但如果我像这样转义 ls 输出for i in "$(ls -1Atu)";,我将得到一次迭代,并且 $i 将完全包含 ls 的输出。
我们该如何解决这个问题呢?我应该注意到,ls -1Atu根据我的需要,任何具有更好的变体的结果文件将按时间排序。谢谢

答案1

背景阅读:为什么我的 shell 脚本会因为空格或其他特殊字符而卡住?,为什么你不应该解析 ls 的输出

设置IFS为换行符意味着在命令替换扩展期间,只有换行符,而不是空格和制表符,才会被视为分隔符。您的方法不支持包含换行符的文件名;这是 1 的一个基本限制ls

此外,这就是您遇到的问题,设置IFS对命令替换扩展时发生的其他事情(即通配符匹配)没有影响。您可以关闭通配符,然后只要文件名不包含换行符,您的脚本就可以工作。

IFS='
'
set -- *"$IFS"*
if [ -e "$1" ]; then
  echo >&2 "There are file names with newlines. I cannot cope with this."
  exit 2
fi
set -f
for i in $(ls -1Atu); do
    printf '%s\n' "$i"
done
set +f
unset IFS

按日期枚举文件的简单、可靠的方法是使用纯 Bourne 风格的 shell 以外的工具,例如 zsh,它具有全局限定符修改通配符匹配的排序方式。

#!/bin/zsh
files_by_access_time=(*(Doa))

1您可以通过一些实现来解决这个问题ls,但如果您需要可移植性,则不行。

答案2

由于与空白问题等相关的各种原因,不建议解析输出ls。另一种方法是使用 GNU 版本的find, sort, sed

find . -mindepth 1 -maxdepth 1 -printf "%A@ %f\0" | 
  sort -rnz | 
  sed -z 's/^[0-9.]\+ //'
  • find当然,比ls列出和过滤文件要灵活得多,但它本身不进行排序。
  • 在这里,我打印以秒为单位的修改时间 ( %A@),后跟文件名和 ASCII NUL 字符,这是用于分隔文件名的唯一安全字符。
  • sort -rnz然后接受 NUL 分隔的输入 ( -z) 并按-n降序进行数字排序 ( )(因为-tofls首先给出最新的)。
  • 然后我常常sed去掉最初的时间,再次处理 NUL 分隔的行 ( -z)。

生成的输出是 NUL 分隔行的流。在 bash 中(不是普通的 sh),您可以通过以下方式将它们读入变量:

find . -mindepth 1 -maxdepth 1 -printf "%A@ %f\0" | 
  sort -rnz | 
  sed -z 's/^[0-9.]\+ //' |
  while IFS= read -r -d '' filename
  do 
    # do stuff with filenames
    printf "%s\n" "$filename"
  done

当然,这有点复杂,但更安全。

答案3

  1. 您真的是要添加*文件名吗?
  2. 或者您的意思是如果具有执行权限,则ls给出以结尾的文件名的输出?*

如果只有 的输出问题ls,您可以简单地通过以下方式解决:

替换ls\ls,这是使用 的非别名版本ls,它不输出*

答案4

你可以使用这样的东西:

ls -1Atu | while IFS= read -r entry; do
    echo "$entry"
done

在这个示例中,输出生成一次,并且该while read entry部分导致输出被ls逐行解析,这解决了for示例中所有内容都放在$i单轮中的问题。

相关内容