有没有办法通过 glob 反转文件列表?
所以我会得到与以下相同的结果:
ls -r *
我在 shell 脚本中使用它并shellcheck
不断抱怨:
^--------^ SC2045:迭代 ls 输出很脆弱。使用全局。
答案1
桀骜
使用shell,可以使用(order)、 (与反向相同)和(对于数字)glob 限定符zsh
来自定义 glob 的排序顺序,oC
OC
^oC
n
引用info zsh qualifier
:
oC
指定文件名称的排序方式。如果是 C
n
,则按名称排序;如果是,L
则根据文件的大小(长度)进行排序;如果l
它们按链接数量排序; ifa
、m
、 或者c
它们分别按上次访问、修改或 inode 更改的时间排序;如果d
,子目录中的文件在每个搜索级别都出现在当前目录中的文件之前 - 这最好与其他条件结合使用,例如“odon
”以对同一目录中的文件名称进行排序;如果N
,则不执行排序。请注意a
, 、m
、 并将c
年龄与当前时间进行比较,因此列表中的第一个名称是最年轻的文件。另请注意,使用了修饰符^
和 ,因此 ' ' 给出了所有文件的列表,按文件大小降序排列,紧随任何符号链接。除非 使用 ,否则可能会出现多个顺序说明符来解决关系。-
*(^-oL)
oN
默认排序是
n
(按名称),除非Y
使用了 glob 限定符,在这种情况下它是N
(未排序)。
oe
并且o+
是特殊情况;它们各自后面跟着 shell 代码,分别按照e
glob 限定符和+
glob 限定符进行分隔(见上文)。针对每个匹配的文件执行代码,参数REPLY
设置为条目上的文件名并附globsort
加到zsh_eval_context
.代码应该REPLY
以某种方式修改参数。返回时,使用参数的值而不是文件名作为要排序的字符串。与其他排序运算符不同,oe
ando+
可以重复,但请注意,任何 glob 表达式中可能出现的任何类型的排序运算符的最大数量为 12。
因此,在这里,要按字母顺序倒序获取文件列表:
list=(*(NOn))
或者
list=(*(N^on))
(这里还使用N
(for nullglob
) 限定符,以便如果没有匹配的文件,列表将变为空)。
对于按修改时间反向排序的列表(如ls -rt
):
list=(*(NOm))
o
使用 zsh,您还可以使用和O
参数扩展标志对数组元素进行排序。
list=(*(N))
list_reversed=(${(Oa)list})
也就是说,对数组成员$list
进行反向排序(因为大写O
)a
。
巴什
使用最新版本的bash
shell 和 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
(假设所有文件名都是语言环境中的有效文本)、ksh93
和zsh
,mksh
但对于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。因此,您需要使用ls
、sort
或类似的东西来做这件事。看shellcheck 维基为什么迭代 的输出ls
很脆弱,以及如何处理它。基本上,有两个主要问题需要注意:1)当目录为空时,默认echo *
打印。 *
2)如果您的文件名称中包含空格,则可能会混淆读取输入的内容。对于 (1),如果您使用的是 bash,则需要设置该nullglob
选项。对于 (2),您可能需要按照以下方式编写一些内容
ls -1r | while read filename; do
...
done
(再次假设您正在使用sh
、bash
或zsh
。)
答案4
可移植到大多数 shell,避免一个文件出错(仅使用seq
和 shell 给出的printf
, set
and eval
):
set -- *
[ $# -gt 1 ] && eval set -- "$(printf "\"\${%s}\" " $(seq "$#" -1 1))"
printf '%s ' "$@"
在可用的情况下设置 nullglob 和 failedglob 是合理的。