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


158

说,我有一个foo.txt指定N参数的文件

arg1
arg2
...
argN

我需要传递给命令 my_command

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


10
“参数”是单数形式还是“参数”?接受的答案仅在单参数的情况下才是正确的(这是正文要查询的内容),而在多参数的情况下则是正确的,主题似乎在此情况下进行查询。
Charles Duffy 2014年

下面的4个答案通常具有相同的结果,但是它们的语义略有不同。现在,我记得为什么我停止编写bash脚本了:P
Navin,

Answers:


236

如果您的shell是bash(以及其他),则快捷方式$(cat afile)$(< afile),因此您应编写:

mycommand "$(< file.txt)"

在“命令替换”部分的bash手册页中记录。

或者,从stdin中读取命令,因此: mycommand < file.txt


11
要学究,使用<操作符不是捷径。这意味着外壳本身将执行重定向,而不是cat为其重定向属性执行二进制文件。
lstyls

2
这是一个内核设置,因此您需要重新编译内核。或调查xargs命令
格伦·杰克曼(Glenn jackman)

3
@ykaner,因为如果没有"$(…)",则的内容file.txt将传递给的标准输入mycommand,而不作为参数。"$(…)"表示运行命令并以字符串形式返回输出 ; 在这里,“命令”仅读取文件,但可能更复杂。
törzsmókus

3
除非我失去引号,否则它对我不起作用。用引号将整个文件作为1参数。不带引号,它将每一行解释为一个单独的arg。
皮特

1
@LarsH你是绝对正确的。谢谢,这是一种不太混乱的方式。
lstyls

37

如前所述,您可以使用反引号或$(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上应相同。
威尔

1
read -r除非您要扩展反斜杠转义序列,否则应该为Read。NUL是比换行符更安全的定界符,尤其是当您传递的参数是文件名之类的东西时,其中可以包含文字换行符。另外,如果不清除IFS,则会从中隐式清除前导和尾随空格i
Charles Duffy 2014年

18
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. 对于循环语法:https : //www.cyberciti.biz/faq/bash-for-loop/

同样,放入< file反引号意味着它实际上根本不会执行重定向command
查尔斯·达菲

3
(支持此方法的人是否真的进行过测试?在bash或POSIX sh中都command `< file` 不起作用; zsh是另一回事,但这不是此问题所针对的shell)。
查尔斯·达菲

@CharlesDuffy您能否更具体地了解它如何工作?
mwfearnley '18

@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
查尔斯·达菲

15

您可以使用反引号:

echo World > file.txt
echo Hello `cat file.txt`

4
这不会创建一个说法-因为它是没有报价,它的主题串分裂,所以如果你发出echo "Hello * Starry * World" > file.txt的第一个步骤,你会得到传递给第二个命令至少四个单独的参数-而且可能,因为*s会扩展为当前目录中存在的文件名。
查尔斯·达菲

3
...并且由于它正在运行glob扩展,因此它不会确切地发出文件中的内容。而且由于它正在执行命令替换操作,因此效率极低-实际上,它正在fork()关闭一个子容器,并在其stdout上附加了FIFO,然后/bin/cat作为该子壳的子级进行调用,然后通过FIFO读取输出;与相比$(<file.txt),它直接将文件的内容读取到bash中,而不涉及子shell或FIFO。
查尔斯·达菲

11

如果您想以一种健壮的方式来执行此操作,以使其适用于每个可能的命令行参数(带空格的值,带换行的值,带文字引号字符的值,不可打印的值,带glob字符的值等),它会有点更有趣的。


在给定参数数组的情况下写入文件:

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

这是我将文件内容作为参数传递给命令的方式:

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

1
这等效于-但实际上效率较低-有效等效于$(<bar.txt)(使用带适当扩展名的shell(如bash)时)。$(<foo)这是一种特殊情况:与中使用的常规命令替换不同$(cat ...),它不会派生子shell来进行操作,因此避免了很多开销。
Charles Duffy

3

如果您要做的就是打开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


1

在我的bash外壳中,以下内容就像一个咒语:

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

其中input_file是

arg1
arg2
arg3

显而易见,这使您可以从input_file的每一行执行多个命令,这是我在此处学到的一个不错的小技巧。


3
从安全角度来看,您的“妙招”很危险-如果您的参数包含$(somecommand),您将执行该命令而不是将其作为文本传递。同样地,>/etc/passwd将被处理为重定向和重写/etc/passwd(如果具有适当权限运行)等
查尔斯达菲

2
相反,(在具有GNU扩展名的系统上)执行以下操作更安全:xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _-而且效率更高,因为它会将尽可能多的参数传递给每个shell,而不是在输入文件中每行启动一个shell。
查尔斯·达菲

当然,cat
也会

0

几次编辑@Wesley Rice的答案后,我认为我的更改太大了,无法继续更改他的答案,而不是自己编写。因此,我决定需要自己编写!

读入文件的每一行并逐行对其进行操作,如下所示:

#!/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传递给read命令的选项防止反斜杠转义被解释。
4. IFS=在读取命令前添加选项,以防止前导/后缀空白被修剪
-5。while IFS= read -r line; do COMMAND_on $line; done < input.file


现在,我要回答这个已经关闭的问题:是否可以通过git add从文件中添加文件列表?-这是我的答案:

请注意,这FILES_STAGED是一个包含文件绝对路径的变量,该文件包含一堆行,其中每一行都是我要执行的文件的相对路径git add。此代码段即将成为该项目中“ eRCaGuy_dotfiles / useful_scripts / sync_git_repo_to_build_machine.sh”文件的一部分,以使开发中的文件能够轻松地从一台PC(例如,我编写过代码的计算机)同步到另一台PC(例如:我构建的功能更强大的计算机):https : //github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles

while IFS= read -r line
do
    echo "  git add \"$line\""
    git add "$line" 
done < "$FILES_STAGED"

参考文献:

  1. 我从哪里复制答案: https //www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
  2. 对于循环语法:https : //www.cyberciti.biz/faq/bash-for-loop/

有关:

  1. 如何逐行读取文件内容并执行git add以下操作:是否可以从文件中“ git add”文件列表?
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.