如何使用文件的行作为命令的参数?
Posted
技术标签:
【中文标题】如何使用文件的行作为命令的参数?【英文标题】:How do I use the lines of a file as arguments of a command? 【发布时间】:2011-05-12 19:06:17 【问题描述】:说,我有一个文件 foo.txt
指定 N
参数
arg1
arg2
...
argN
我需要将其传递给命令my_command
【问题讨论】:
“arguments”,复数,或“argument”,单?接受的答案仅在单参数情况下是正确的 - 这是正文要查询的内容 - 但在主题似乎要询问的多参数情况下不是正确的。 下面的 4 个答案通常具有相同的结果,但它们的语义略有不同。现在我记得我为什么停止编写 bash 脚本了:P 【参考方案1】:如果你的 shell 是 bash(以及其他),$(cat afile)
的快捷方式是 $(< afile)
,所以你会写:
mycommand "$(< file.txt)"
记录在 bash 手册页的“命令替换”部分。
或者,从标准输入读取你的命令,所以:mycommand < file.txt
【讨论】:
为了迂腐,使用<
运算符不是捷径。这意味着 shell 本身将执行重定向,而不是针对其重定向属性执行 cat
二进制文件。
这是一个内核设置,因此您需要重新编译内核。或者调查 xargs 命令
@ykaner 因为没有"$(…)"
,file.txt
的内容将被传递给mycommand
的标准输入,而不是作为参数。 "$(…)"
表示运行命令并将输出作为字符串返回;这里的“命令”只读取文件,但它可能更复杂。
除非我丢失引号,否则它对我不起作用。使用引号,它将整个文件作为 1 个参数。没有引号,它将每一行解释为一个单独的参数。
@lstyls:我不确定您所说的“这不是捷径”是什么意思。让 shell 执行重定向而不是生成子 cat
进程听起来会节省资源。裁判员将这种替换描述为“等效但更快”。也许您的意思是“这不仅仅是键入相同内容的更短方式”?【参考方案2】:
如前所述,您可以使用反引号或$(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 或类似的。 IIRC,这个语法在 ksh 和 zsh 上应该是一样的。 Read 应该是read -r
除非你想扩展反斜杠转义序列——并且 NUL 是比换行符更安全的分隔符,特别是如果你传递的参数是文件名之类的东西,可以包含文字换行符。此外,如果不清除 IFS,您会从 i
中隐式清除前导和尾随空格。【参考方案3】:
command `< file`
会将文件内容传递给标准输入上的命令,但会去除换行符,这意味着您无法单独遍历每一行。为此,您可以编写一个带有“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
参考资料:
-
for 循环语法:https://www.cyberciti.biz/faq/bash-for-loop/
【讨论】:
另外,将< file
放在反引号中意味着它实际上根本不会对command
执行重定向。
(赞成这个的人真的测试过吗?command `< file`
在 bash 或 POSIX sh 中都不起作用;zsh 是另一回事,但这不是壳这个问题是关于)。
@CharlesDuffy 你能更具体地说明它是如何不起作用的吗?
@CharlesDuffy 这在 bash 3.2 和 zsh 5.3 中有效。这在 sh、ash 和 dash 中不起作用,但在 $(Hello World
放在一行中。你会看到它首先运行 some_command Hello
,然后是 some_command World
,not some_command "Hello World"
或 some_command Hello World
。【参考方案4】:
你使用反引号来做到这一点:
echo World > file.txt
echo Hello `cat file.txt`
【讨论】:
这不会创建 an 参数——因为它没有被引用,它会被字符串分割,所以如果你在第一步发出echo "Hello * Starry * World" > file.txt
,你会得到至少四个单独的参数传递给第二个命令——而且可能更多,因为*
s 将扩展为当前目录中存在的文件的名称。
...因为它正在运行 glob 扩展,所以它不会发出确切文件中的内容。 而且 因为它正在执行命令替换操作,所以效率极低——它实际上是 fork()
关闭一个附加到其标准输出的 FIFO 的子外壳,然后调用 /bin/cat
作为该子外壳的子外壳,然后通过 FIFO 读取输出;与 $(<file.txt)
相比,后者直接将文件内容读入 bash,不涉及子 shell 或 FIFO。【参考方案5】:
如果您想以一种适用于每个可能的命令行参数(带有空格的值、带有换行符的值、带有文字引号字符的值、不可打印的值、带有全局字符的值等)的稳健方式执行此操作,它变得更有趣了。
要写入文件,给定参数数组:
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[@]"
从该文件中读取并使用其内容(在没有数组的 shell 中;请注意,这将覆盖您的本地命令行参数列表,因此最好在函数内部完成,这样您就可以覆盖函数的参数而不是全局列表):
set --
while IFS='' read -r -d '' item; do
set -- "$@" "$item"
done <file
run_your_command "$@"
请注意,-d
(允许使用不同的行尾分隔符)是非 POSIX 扩展,没有数组的 shell 也可能不支持它。如果是这种情况,您可能需要使用非 shell 语言将 NUL 分隔的内容转换为 eval
-safe 形式:
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 "$@"
【讨论】:
【参考方案6】:如果您需要做的只是将文件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
【讨论】:
【参考方案7】:以下是我将文件内容作为参数传递给命令的方法:
./foo --bar "$(cat ./bar.txt)"
【讨论】:
这 - 但效率大大降低 - 等效于$(<bar.txt)
(当使用带有适当扩展名的 shell,例如 bash)。 $(<foo)
是一种特殊情况:与$(cat ...)
中使用的常规命令替换不同,它不会派生子shell 进行操作,因此避免了大量开销。【参考方案8】:
似乎没有一个答案对我有用,或者太复杂了。幸运的是,xargs
并不复杂(在 Ubuntu 20.04 上测试)。
正如 OP 所提到的,这适用于文件中单独一行上的每个 arg,这也是我所需要的。
cat foo.txt | xargs my_command
需要注意的一点是,它似乎不适用于别名命令。
如果命令接受包含在字符串中的多个参数,则接受的答案有效。在我使用 (Neo)Vim 的情况下,它没有,并且所有参数都粘在一起。
xargs
正确执行,实际上为您提供了提供给命令的单独参数。
【讨论】:
【参考方案9】:在我的 bash shell 中,下面的工作就像一个魅力:
cat input_file | xargs -I % sh -c 'command1 %; command2 %; command3 %;'
input_file 在哪里
arg1
arg2
arg3
很明显,这允许您使用 input_file 中的每一行执行多个命令,这是我学到的一个不错的小技巧 here。
【讨论】:
从安全角度来看,您的“小技巧”很危险——如果您有一个包含$(somecommand)
的参数,您将执行该命令而不是作为文本传递。同样,>/etc/passwd
将作为重定向处理并覆盖/etc/passwd
(如果以适当的权限运行)等。
执行以下操作会更安全(在具有 GNU 扩展的系统上):xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _
-- 并且效率更高,因为它向每个 shell 传递尽可能多的参数,而不是启动一个输入文件中每行的 shell。
当然,丢掉useless use of cat
【参考方案10】:
我建议使用:
command $(echo $(tr '\n' ' ' < parameters.cfg))
只需修剪结束行字符并将它们替换为空格,然后将生成的字符串作为可能的单独参数与 echo 推送。
【讨论】:
【参考方案11】:在编辑@Wesley Rice's answer 几次之后,我决定我的更改太大了,无法继续更改他的答案,而不是自己写。所以,我决定我需要自己写!
读取文件的每一行并像这样逐行操作:
#!/bin/bash
input="/path/to/txt/file"
while IFS= read -r line
do
echo "$line"
done < "$input"
这直接来自作者 Vivek Gite:https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/。他得到了荣誉!
语法:在 Bash Unix 和 Linux shell 上逐行读取文件: 1. bash、ksh、zsh等shell逐行读取文件的语法如下 2.
while read -r line; do COMMAND; done < input.file
3. 传递给读取命令的-r
选项可防止反斜杠转义被解释。 4.在读取命令前添加IFS=
选项,防止前导/尾随空格被修剪- 5.while IFS= read -r line; do COMMAND_on $line; done < input.file
现在来回答这个我也有的现已结束的问题:Is it possible to `git add` a list of files from a file? - 这是我的答案:
请注意,FILES_STAGED
是一个变量,其中包含一个文件的绝对路径,该文件包含一堆行,其中每一行都是我想要在 git add
上执行的文件的相对路径。这段代码 sn-p 即将成为该项目中 "eRCaGuy_dotfiles/useful_scripts/sync_git_repo_to_build_machine.sh" 文件的一部分,以便从一台 PC 轻松同步开发中的文件(例如:我的计算机代码)到另一个(例如:我构建的更强大的计算机):https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles。
while IFS= read -r line
do
echo " git add \"$line\""
git add "$line"
done < "$FILES_STAGED"
参考资料:
-
我从哪里复制了我的答案:https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
For 循环语法:https://www.cyberciti.biz/faq/bash-for-loop/
相关:
-
如何逐行读取文件内容并对其执行
git add
:Is it possible to `git add` a list of files from a file?
【讨论】:
以上是关于如何使用文件的行作为命令的参数?的主要内容,如果未能解决你的问题,请参考以下文章