简体   繁体   English

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

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

I am using the bash shell and want to execute a command that takes filenames as arguments;我正在使用 bash shell 并想要执行将文件名作为 arguments 的命令; say the cat command.说 cat 命令。 I need to provide the arguments sorted by modification time (oldest first) and unfortunately the filenames can contain spaces and a few other difficult characters such as "-", "[", "]".我需要提供按修改时间排序的 arguments(最旧的在前),不幸的是,文件名可能包含空格和一些其他困难字符,例如“-”、“[”、“]”。 The files to be provided as arguments are all the *.txt files in my directory.提供为 arguments 的文件是我目录下的所有 *.txt 文件。 I cannot find the right syntax.我找不到正确的语法。 Here are my efforts.这是我的努力。

Of course, cat *.txt fails;当然, cat *.txt失败; it does not give the desired order of the arguments.它没有给出 arguments 的所需顺序。

cat `ls -rt *.txt`

The `ls -rt *.txt` gives the desired order, but now the blanks in the filenames cause confusion; `ls -rt *.txt`给出了所需的顺序,但现在文件名中的空格会引起混淆; they are seen as filename separators by the cat command.它们被 cat 命令视为文件名分隔符。

cat `ls -brt *.txt`

I tried -b to escape non-graphic characters, but the blanks are still seen as filename separators by cat.我尝试-b转义非图形字符,但空白仍然被 cat 视为文件名分隔符。

cat `ls -Qrt *.txt`

I tried -Q to put entry names in double quotes.我尝试-Q将条目名称放在双引号中。

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

I tried this and other variants of the quoting style.我尝试了这个和其他引用风格的变体。

Nothing that I've tried works.我尝试过的任何方法都不起作用。 Either the blanks are treated as filename separators by cat, or the entire list of filenames is treated as one (invalid) argument. cat 将空格视为文件名分隔符,或者将整个文件名列表视为一个(无效)参数。 Please advise!请指教!

Using --quoting-style is a good start.使用--quoting-style是一个好的开始。 The trick is in parsing the quoted file names.诀窍在于解析引用的文件名。 Backticks are simply not up to the job.反引号根本无法胜任这项工作。 We're going to have to be super explicit about parsing the escape sequences.我们必须非常明确地解析转义序列。

First, we need to pick a quoting style.首先,我们需要选择一种引用风格。 Let's see how the various algorithms handle a crazy file name like "foo 'bar'\tbaz\nquux" .让我们看看各种算法如何处理像"foo 'bar'\tbaz\nquux"这样的疯狂文件名。 That's a file name containing actual single and double quotes, plus a space, tab, and newline to boot.这是一个包含实际单引号和双引号的文件名,以及用于引导的空格、制表符和换行符。 If you're wondering: yes, these are all legal, albeit unusual.如果您想知道:是的,这些都是合法的,尽管不寻常。

$ 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"’>

The ones that actually span two lines are no good, so literal , shell , and shell-always are out.实际上跨越两行的那些不好,所以literal shellshell-always都出来了。 Smart quotes aren't helpful, so locale and clocale are out.智能引号没有帮助,因此localeclocale不可用。 Here's what's left:这是剩下的:

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">

Which of these can we work with?我们可以使用其中哪些? Well, we're in a shell script.好吧,我们在 shell 脚本中。 Let's use shell-escape .让我们使用shell-escape

There will be one file name per line.每行将有一个文件名。 We can use a while read loop to read a line at a time.我们可以使用while read循环一次读取一行。 We'll also need IFS= and -r to disable any special character handling.我们还需要IFS=-r来禁用任何特殊字符处理。 A standard line processing loop looks like this: 标准行处理循环如下所示:

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

That "file" at the end is supposed to be a file name, but we don't want to read from a file, we want to read from the ls command.最后的那个“文件”应该是一个文件名,但我们不想从文件中读取,我们想从ls命令中读取。 Let's use <(...) process substitution to swap in a command where a file name is expected.让我们使用<(...) 进程替换来交换需要文件名的命令。

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

Now we need to convert each line with all the quoted characters into a usable file name.现在我们需要将所有带引号的字符的每一行转换为可用的文件名。 We can use eval to have the shell interpret all the escape sequences.我们可以使用eval让 shell 解释所有转义序列。 (I almost always warn against using eval but this is a rare situation where it's okay.) (我几乎总是警告不要使用eval ,但这是一种可以接受的罕见情况。)

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

If you wanted to work one file at a time we'd be done.如果您想一次处理一个文件,我们就完成了。 But you want to pass all the file names at once to another command.但是您想一次将所有文件名传递给另一个命令。 To get to the finish line, the last step is to build an array with all the file names.要到达终点,最后一步是构建一个包含所有文件名的数组。

files=()

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

cat "${files[@]}"

There we go.我们有 go。 It's not pretty.这不漂亮。 It's not elegant.它不优雅。 But it's safe.但它是安全的。

Does this do what you want?这是做你想做的吗?

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM