使用 bash,如何将文件名参数传递给按日期排序并处理空格和其他特殊字符的命令?

Using bash, how to pass filename arguments to a command sorted by date and dealing with spaces and other special characters?

我正在使用 bash shell 并想执行一个以文件名作为参数的命令;说猫命令。我需要提供按修改时间排序的参数(最早的在前),不幸的是,文件名可能包含空格和其他一些难以理解的字符,例如“-”、“[”、“]”。要作为参数提供的文件是我目录中的所有 *.txt 文件。我找不到正确的语法。这是我的努力。

当然,cat *.txt失败了;它没有给出所需的参数顺序。

cat `ls -rt *.txt`

`ls -rt *.txt` 给出了所需的顺序,但现在文件名中的空格会造成混淆;它们被 cat 命令视为文件名分隔符。

cat `ls -brt *.txt`

我尝试 -b 转义非图形字符,但空格仍被 cat 视为文件名分隔符。

cat `ls -Qrt *.txt`

我试过 -Q 将条目名称放在双引号中。

cat `ls -rt --quoting-style=escape *.txt`

我尝试了这种和其他引用风格的变体。

我试过的都不起作用。要么空格被 cat 视为文件名分隔符,要么整个文件名列表被视为一个(无效的)参数。 请指教!

这是你想要的吗?

for i in $(ls -rt *.txt); do echo "FILE: $i"; cat "$i"; done

使用 --quoting-style 是一个好的开始。诀窍在于解析引用的文件名。反引号根本无法胜任这项工作。我们必须非常明确地解析转义序列。

首先,我们需要选择一种引用风格。让我们看看各种算法如何处理像 "foo 'bar'\tbaz\nquux" 这样疯狂的文件名。这是一个文件名,包含实际的单引号和双引号,加上 space、制表符和引导换行符。如果您想知道:是的,这些都是合法的,尽管不常见。

$ for style in literal shell shell-always shell-escape shell-escape-always c c-maybe escape locale clocale; do printf '%-20s <%s>\n' "$style" "$(ls --quoting-style="$style" '"foo '\''bar'\'''$'\t''baz '$'\n''quux"')"; done
literal              <"foo 'bar'    baz 
quux">
shell                <'"foo '\''bar'\'' baz 
quux"'>
shell-always         <'"foo '\''bar'\'' baz 
quux"'>
shell-escape         <'"foo '\''bar'\'''$'\t''baz '$'\n''quux"'>
shell-escape-always  <'"foo '\''bar'\'''$'\t''baz '$'\n''quux"'>
c                    <"\"foo 'bar'\tbaz \nquux\"">
c-maybe              <"\"foo 'bar'\tbaz \nquux\"">
escape               <"foo\ 'bar'\tbaz\ \nquux">
locale               <‘"foo 'bar'\tbaz \nquux"’>
clocale              <‘"foo 'bar'\tbaz \nquux"’>

实际跨越两行的不行,所以literalshellshell-alwaysout了。智能引号没有帮助,因此 localeclocale 已被淘汰。这是剩下的:

shell-escape         <'"foo '\''bar'\'''$'\t''baz '$'\n''quux"'>
shell-escape-always  <'"foo '\''bar'\'''$'\t''baz '$'\n''quux"'>
c                    <"\"foo 'bar'\tbaz \nquux\"">
c-maybe              <"\"foo 'bar'\tbaz \nquux\"">
escape               <"foo\ 'bar'\tbaz\ \nquux">

我们可以使用其中哪些?好吧,我们在 shell 脚本中。让我们使用 shell-escape.

每行一个文件名。我们可以使用 while read 循环一次读取一行。我们还需要 IFS=-r 来禁用任何特殊字符处理。 standard line processing loop 看起来像这样:

while IFS= read -r line; do ... done < file

那末尾的"file"应该是文件名,但是我们不想从文件中读取,我们想从ls命令中读取。让我们使用 <(...) process substitution 在需要文件名的命令中交换。

while IFS= read -r line; do
    # process each line
done < <(ls -rt --quoting-style=shell-escape *.txt)

现在我们需要将包含所有引用字符的每一行转换为可用的文件名。我们可以使用 eval 让 shell 解释所有转义序列。 (我几乎总是 warn against using eval 但这种情况很少见。)

while IFS= read -r line; do
    eval "file=$line"
done < <(ls -rt --quoting-style=shell-escape *.txt)

如果您想一次处理一个文件,我们就完成了。但是您想一次将所有文件名传递给另一个命令。要到达终点线,最后一步是构建一个包含所有文件名的数组。

files=()

while IFS= read -r line; do
    eval "files+=($line)"
done < <(ls -rt --quoting-style=shell-escape *.txt)

cat "${files[@]}"

好了。这不是很漂亮。这不优雅。但是很安全。