如何使用文件中的行作为命令的参数?

229

比方说,我有一个文件foo.txt,其中指定了N个参数。

arg1
arg2
...
argN

我需要将文件的行作为参数传递给命令my_command,该如何操作?


16
“arguments”是复数形式,“argument”是单数形式?接受的答案仅适用于单参数情况——这正是正文所询问的——但不适用于多参数情况,而该主题似乎在询问多参数情况。 - Charles Duffy
以下4个答案通常具有相同的结果,但它们的语义略有不同。现在我记得为什么我停止编写bash脚本了:P - Navin
12个回答

307

如果你的shell是bash(还有其他一些),$(cat afile)的一个快捷方式是$(< afile),因此你可以写成:

mycommand "$(< file.txt)"

在bash man页面的“Command Substitution”部分有详细说明。

另一种方法是让您的命令从标准输入读取,例如:mycommand < file.txt


25
严谨来说,使用 < 运算符并不是一个快捷方式。它意味着 shell 本身将执行重定向功能,而不是执行 cat 二进制文件以进行重定向。 - lstyls
2
这是一个内核设置,因此您需要重新编译内核。或者调查xargs命令。 - glenn jackman
4
因为没有"$(…)",所以file.txt的内容会被传递到mycommand的标准输入中,而不是作为一个参数。 "$(…)"的意思是“运行命令并将输出作为字符串返回”;在这里,“命令”只是读取文件,但也可以更复杂。 - törzsmókus
9
除非我删除引号,否则它对我无效。使用引号会将整个文件作为一个参数处理。没有引号时,它会将每一行解释为单独的参数。 - Pete
1
@LarsH 您说得非常正确。这是一个不那么令人困惑的表述方式,谢谢。 - lstyls
显示剩余6条评论

45

如先前提到的,你可以使用反引号或 $(cat filename)

但需要注意的是,shell会按照空格将文件内容分解为多个“单词”,并将每个“单词”作为一个参数传递给您的命令。尽管您可以用引号将命令行参数括起来,以便它可以包含空格、转义序列等,但从文件中读取不会执行同样的操作。例如,如果您的文件包含:

a "b c" d

你将得到的参数是:

a
"b
c"
d

如果您想将每一行作为参数提取出来,请使用while/read/do结构:

while read i ; do command_name $i ; done < filename

我应该提到,我假设你正在使用bash。我意识到还有其他的shell,但我所使用过的几乎所有*nix机器都运行bash或某种等效的shell。如果我没记错的话,这个语法在ksh和zsh上应该也能正常工作。 - Will
1
除非您想要展开反斜杠转义序列,否则应该将“read”更改为“read -r”--并且NUL是比换行符更安全的分隔符,特别是如果您传递的参数是像文件名这样可能包含文字换行符的东西。此外,如果不清除IFS,则会从“i”中隐式清除前导和尾随空格。 - Charles Duffy

33
command `< file`

这将文件内容传递给命令的标准输入(stdin),但会去除换行符,意味着您无法逐行迭代。为此,您可以编写一个带有“for”循环的脚本:

for line in `cat input_file`; do some_command "$line"; done

或者(多行变量):

for line in `cat input_file`
do
    some_command "$line"
done

或者(多行变体使用$()而不是``):

for line in $(cat input_file)
do
    some_command "$line"
done

参考资料:

  1. for循环语法: https://www.cyberciti.biz/faq/bash-for-loop/

此外,在反引号中放置 < file 意味着它实际上不会对 command 执行重定向。 - Charles Duffy
4
那些点赞的人有没有真正测试过这个命令? command `< file` 在 bash 和 POSIX sh 中都 无法工作,虽然 zsh 可以,但这不是本问题所涉及的 shell。 - Charles Duffy
@CharlesDuffy 这在bash 3.2和zsh 5.3中是有效的。这在sh、ash和dash中不起作用,但$(< file)也不行。 - Arnie97
1
@mwfearnley,很高兴为您提供这个具体信息。目标是迭代每一行,对吧?将“Hello World”放在一行上。你会看到它首先运行“some_command Hello”,然后是“some_command World”,而不是“some_command“Hello World””或“some_command Hello World”。 - Charles Duffy
这里的for循环选项非常好用!我刚刚添加了一些多行变体,以帮助下一个人。我已经测试过它,在一个文件中调用git add来添加一堆待添加的行。 - Gabriel Staples
显示剩余2条评论

16
你可以使用反引号来实现这个功能:
echo World > file.txt
echo Hello `cat file.txt`

4
这并不会创建“一个”参数,因为它没有加引号,所以如果你在第一步中输入了echo "Hello * Starry * World" > file.txt,则会被视为字符串分割,至少会传递四个单独的参数给第二个命令 - 并且很可能更多,因为星号会扩展成当前目录中存在的文件名。 - Charles Duffy
3
由于它正在运行Glob扩展,所以它不会完全发射文件中的内容。并且由于它执行了命令替换操作,它非常低效 - 实际上它是通过在其标准输出附加FIFO的子shell进行分叉,然后作为该子shell的子进程调用/bin/cat,然后通过FIFO读取输出。相比之下,$(<file.txt)将文件内容直接读入bash中,而无需涉及子shell或FIFO。 - Charles Duffy

15

如果你希望以一种健壮的方式来处理每一个可能的命令行参数(包括带有空格、换行符、文字引号字符、不可打印字符、带有通配符等),那就会变得有点有趣。


为了将其写入文件,给定一个参数数组:

printf '%s\0' "${arguments[@]}" >file

用适当的参数(例如"argument one""argument two"等)替换...。


要从该文件中读取并使用其内容(在bash、ksh93或其他具有数组功能的最新shell中):

declare -a args=()
while IFS='' read -r -d '' item; do
  args+=( "$item" )
done <file
run_your_command "${args[@]}"

在没有使用数组的情况下从该文件中读取并使用其内容(请注意,这将覆盖您本地的命令行参数列表,因此最好在函数内部执行此操作,以便您覆盖函数的参数而不是全局列表):


set --
while IFS='' read -r -d '' item; do
  set -- "$@" "$item"
done <file
run_your_command "$@"

请注意,-d(允许使用不同的行尾分隔符)是一种非POSIX扩展,而没有数组的shell也可能不支持它。如果是这种情况,您可能需要使用非shell语言将以NUL为分隔符的内容转换为eval-安全形式:

quoted_list() {
  ## Works with either Python 2.x or 3.x
  python -c '
import sys, pipes, shlex
quote = pipes.quote if hasattr(pipes, "quote") else shlex.quote
print(" ".join([quote(s) for s in sys.stdin.read().split("\0")][:-1]))
  '
}

eval "set -- $(quoted_list <file)"
run_your_command "$@"

9
如果您只需要转换带有以下内容的文件arguments.txt
arg1
arg2
argN

如果你想将文本转换为 my_command arg1 arg2 argN 的格式,那么你可以使用 xargs 命令:

xargs -a arguments.txt my_command

你可以在xargs调用中添加额外的静态参数,例如xargs -a arguments.txt my_command staticArg,它将调用my_command staticArg arg1 arg2 argN

它只缺少“-d '\n'”,否则对于文件中带有空格的行将无法工作。 - André Willik Valenti

7

对我来说,所有的答案似乎都不起作用或者太复杂了。幸运的是,在Ubuntu 20.04上,使用xargs并不复杂。

正如原帖中提到的那样,这个方法可以处理文件中每个参数在单独一行的情况,这也是我所需要的。

cat foo.txt | xargs my_command

需要注意的一点是,似乎无法使用别名命令。

如果命令接受包含多个参数的字符串,则可接受的答案可行。在我的情况下,(Neo)Vim不支持该方法,因为所有参数都被粘在一起了。

xargs 可以正确地执行并提供传递给命令的单独参数。


< foo.txt xargs my_command 或者 xargs < foo.txt my_command,重定向可以放在任何位置。对于BSD系统,您可以使用 < foo xargs -d \\n cmd 或者 < foo tr \\n \\0 | xargs -0 cmd - CervEd

6

以下是我如何将文件内容作为参数传递给命令:

./foo --bar "$(cat ./bar.txt)"

1
这个代码块虽然效率较低,但与使用适当扩展的shell(如bash)时的$(<bar.txt)基本等效。 $(<foo)是一个特殊情况:与在$(cat ...)中使用的常规命令替换不同,它不会分叉出一个子shell进行操作,从而避免了大量的开销。 - Charles Duffy

3

我建议使用:

command $(echo $(tr '\n' ' ' < parameters.cfg))

只需去除行末字符并用空格替换它们,然后将结果字符串作为可能的单独参数使用echo命令输出。


2

在我的Bash shell中,以下内容可以完美地运行:

cat input_file | xargs -I % sh -c 'command1 %; command2 %; command3 %;'

输入文件是指

arg1
arg2
arg3

正如显而易见的那样,这允许您在每个来自input_file的行中执行多个命令,这是我从这里学到的一个小技巧。


3
从安全角度来看,你的“巧妙小技巧”是危险的。如果你有一个包含 $(somecommand) 的参数,那么这个命令将被执行而不是作为文本传递。同样地,>/etc/passwd 也将被处理为重定向并覆盖 /etc/passwd(如果以适当的权限运行)。 - Charles Duffy
2
在具有GNU扩展的系统上,最好执行以下操作:xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _。这样更安全,也更高效,因为它会尽可能地将多个参数传递给每个shell,而不是针对输入文件中的每一行启动一个shell。 - Charles Duffy
当然,避免使用无用的 cat - tripleee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接