Bash - 使用 glob 打印反向文件列表

Bash - 使用 glob 打印反向文件列表

有没有办法通过 glob 反转文件列表?

所以我会得到与以下相同的结果:

ls -r *

我在 shell 脚本中使用它并shellcheck不断抱怨:

^--------^ SC2045:迭代 ls 输出很脆弱。使用全局。

答案1

桀骜

使用shell,可以使用(order)、 (与反向相同)和(对于数字)glob 限定符zsh来自定义 glob 的排序顺序,oCOC^oCn

引用info zsh qualifier

oC

指定文件名称的排序方式。如果是 C n ,则按名称排序;如果是,L则根据文件的大小(长度)进行排序;如果l它们按链接数量排序; if am、 或者c它们分别按上次访问、修改或 inode 更改的时间排序;如果d,子目录中的文件在每个搜索级别都出现在当前目录中的文件之前 - 这最好与其他条件结合使用,例如“ odon”以对同一目录中的文件名称进行排序;如果N,则不执行排序。请注意a, 、m、 并将c 年龄与当前时间进行比较,因此列表中的第一个名称是最年轻的文件。另请注意,使用了修饰符^和 ,因此 ' ' 给出了所有文件的列表,按文件大小降序排列,紧随任何符号链接。除非 使用 ,否则可能会出现多个顺序说明符来解决关系。-*(^-oL)oN

默认排序是n(按名称),除非Y使用了 glob 限定符,在这种情况下它是N(未排序)。

oe并且o+是特殊情况;它们各自后面跟着 shell 代码,分别按照eglob 限定符和+glob 限定符进行分隔(见上文)。针对每个匹配的文件执行代码,参数REPLY设置为条目上的文件名并附globsort加到zsh_eval_context.代码应该REPLY以某种方式修改参数。返回时,使用参数的值而不是文件名作为要排序的字符串。与其他排序运算符不同,oeando+可以重复,但请注意,任何 glob 表达式中可能出现的任何类型的排序运算符的最大数量为 12。

因此,在这里,要按字母顺序倒序获取文件列表:

list=(*(NOn))

或者

list=(*(N^on))

(这里还使用N(for nullglob) 限定符,以便如果没有匹配的文件,列表将变为空)。

对于按修改时间反向排序的列表(如ls -rt):

list=(*(NOm))

o使用 zsh,您还可以使用和O参数扩展标志对数组元素进行排序。

list=(*(N))
list_reversed=(${(Oa)list})

也就是说,对数组成员$list进行反向排序(因为大写Oa

巴什

使用最新版本的bashshell 和 GNU sort,您可以获得反向排序的文件名列表:

readarray -td '' list < <(
   shopt -s nullglob
   set -- *
   (($# == 0)) || printf '%s\0' "$@" | sort -rz)

readarray -td '' array将 NUL 分隔的记录列表读入数组。

对于 的 GNU 实现ls,另一种方法是:

eval "list=($(ls --quoting-style=shell-always -r))"

wherels --quoting-style=shell-always使用单引号来引用文件名(并\'在单引号之外引用单引号本身)。

该方法也适用于yash(假设所有文件名都是语言环境中的有效文本)、ksh93zshmksh但对于ksh93,请确保事先将变量声明为数组(使用typeset -a list),否则如果ls不提供输出,则会创建$list为复合变量而不是数组变量。

请注意,ast-open 的ls实现也支持一个--quoting-style=shell-always选项,但它使用$'...'引用的形式,这在所有语言环境中使用并不安全。

POSIXly

POSIXly,在 中sh,要反转"$@"数组,您可以执行以下操作:

eval "set -- $(awk 'BEGIN {for (i = ARGV[1]; i; i--) printf " \"${"i"}\""}' "$#")"

这个想法是让 awk 输出类似set -- "${3}" "${2}" "${1}""$@"有 3 个元素时的输出

因此,要以相反的顺序获取文件列表:

set -- *
eval "set -- $(awk 'BEGIN {for (i = ARGV[1]; i; i--) printf " \"${"i"}\""}' "$#")"
echo file list:
printf ' - %s\n' "$@"

请注意,POSIXsh没有任何等效的nullglob选项(一项zsh发明)、zsh 的(N)glob 限定符或 ksh93 的~(N)glob 运算符。如果当前目录中没有非隐藏文件,您最终会得到一个只有一个*元素的列表。解决这个问题的常见方法是:

set -- [*] *
case "$1$2" in
  ('[*]*') shift 2;;
  (*)      shift;;
esac

其中[*]“与”的组合*是区分来自“a”*的一种方式不匹配(其中[*]也将扩展为[*])和一个来自字面名称的文件*(其中[*]将扩展为*)。


无论如何,如果您要传递一个文件名列表以供ls其排序,就像您的ls -r *方法中 shell 将扩展传递*给它一样,您将需要使用该-d选项并标记选项的结尾和--

ls -rd -- *

但是,该输出仍然无法可靠地进行后处理,因为文件名由换行符分隔,并且换行符与文件名中的任何字符一样有效。

答案2

假设这个问题是关于反转文件名通配模式产生的列表,而不是重新实现ls -r.

将全局匹配的结果获取到例如位置参数中:

set -- *

然后反转该列表:

names=()
for name do
    shift
    names[$#]=$name
done

这将创建一个名为 的数组names,其中包含反转的通配模式的匹配项*

反转是通过按顺序迭代位置参数(匹配结果)来完成的,并且对于每个条目,将其插入到反转列表中的位置。$#是位置参数的数量(与我们的通配模式匹配),并shift从该列表中删除一个,$#在每次迭代中减一,因此我们将元素从位置参数列表的开头插入到数组的末尾names

获得names数组后,打印它:

printf '%s\n' "${names[@]}"

...或者做任何你需要用它做的事情。

这会不是ls -r *与该命令相同,ls将列出与 glob 匹配的任何目录的内容*

当模式不匹配任何内容时,bash将使模式保持不展开状态。用于shopt -s nullglob设置 shell 的nullglob选项,激活该选项后,当模式与任何名称不匹配时,将完全删除该模式。要匹配隐藏名称,请另外设置 shell 的dotglob选项。

答案3

你没有说你正在使用哪个 shell,我从来没有遇到过除了字母数字升序之外的任何 shell。因此,您需要使用lssort或类似的东西来做这件事。看shellcheck 维基为什么迭代 的输出ls很脆弱,以及如何处理它。基本上,有两个主要问题需要注意:1)当目录为空时,默认echo *打印。 *2)如果您的文件名称中包含空格,则可能会混淆读取输入的内容。对于 (1),如果您使用的是 bash,则需要设置该nullglob选项。对于 (2),您可能需要按照以下方式编写一些内容

ls -1r | while read filename; do
    ...
done

(再次假设您正在使用shbashzsh。)

答案4

可移植到大多数 shell,避免一个文件出错(仅使用seq和 shell 给出的printf, setand eval):

set -- *
[ $# -gt 1 ] && eval set -- "$(printf "\"\${%s}\" " $(seq "$#" -1 1))"
printf '%s '   "$@"

在可用的情况下设置 nullglob 和 failedglob 是合理的。

相关内容