如何为文件的每一行运行命令?
Posted
技术标签:
【中文标题】如何为文件的每一行运行命令?【英文标题】:How do you run a command for each line of a file? 【发布时间】:2012-12-06 00:25:02 【问题描述】:例如,现在我正在使用以下内容来更改我将其 Unix 路径写入文件的几个文件:
cat file.txt | while read in; do chmod 755 "$in"; done
有没有更优雅、更安全的方法?
【问题讨论】:
【参考方案1】:逐行读取文件并执行命令:4个答案
这是因为不仅有 1 个答案...
shell
命令行扩展
xargs
专用工具
while read
附一些评论
while read -u
使用专用fd
,用于交互式处理(示例)
关于 OP 请求:在文件中列出的所有目标上运行 chmod
,xargs
是指定的工具。但是对于其他一些应用程序,少量文件等......
0。读取整个文件作为命令行参数。
If your file is not too big and all files are *well named* (without spaces or other special chars like quotes), you could use *`shell` command line expansion*. Simply:
chmod 755 $(<file.txt)
For small amount of files (lines), this command is the lighter one.
1。 xargs
是正确的工具
For bigger amount of files, or almost ***any*** number of lines in your input file...
For many *binutils* tools, like `chown`, `chmod`, `rm`, `cp -t` ...
xargs chmod 755 <file.txt
If you have special chars and/or a lot of lines in `file.txt`.
xargs -0 chmod 755 < <(tr \\n \\0 <file.txt)
if your command need to be run exactly 1 time by entry:
xargs -0 -n 1 chmod 755 < <(tr \\n \\0 <file.txt)
This is not needed for this sample, as `chmod` accept multiple files as argument, but this match the title of question.
For some special case, you could even define location of file argument in commands generateds by `xargs`:
xargs -0 -I '' -n 1 myWrapper -arg1 -file='' wrapCmd < <(tr \\n \\0 <file.txt)
以seq 1 5
作为输入进行测试
试试这个:
xargs -n 1 -I echo Blah blabla .. < <(seq 1 5)
Blah 1 blabla 1..
Blah 2 blabla 2..
Blah 3 blabla 3..
Blah 4 blabla 4..
Blah 5 blabla 5..
命令在哪里完成每行一次。
2。 while read
和变体。
正如 OP 建议的那样,cat file.txt | while read in; do chmod 755 "$in"; done
可以工作,但有两个问题:
- `cat |` is an *useless fork*, and
- `| while ... ;done` will become a *subshell* where environment will disapear after `;done`.
所以这样写可以更好:
while read in; do chmod 755 "$in"; done < file.txt
但是,
您可能会收到有关 $IFS
和 read
标志的警告:
help read
> read: read [-r] ... [-d delim] ... [name ...]
... Reads a single line from the standard input... The line is split into fields as with word splitting, and the first word is assigned to the first NAME, the second word to the second NAME, and so on... Only the characters found in $IFS are recognized as word delimiters. ... Options: ... -d delim continue until the first character of DELIM is read, rather than newline ... -r do not allow backslashes to escape any characters ... Exit Status: The return code is zero, unless end-of-file is encountered...
In some case, you may need to use
while IFS= read -r in;do chmod 755 "$in";done <file.txt
For avoiding problems with stranges filenames. And maybe if you encouter problems with *`UTF-8`*:
while LANG=C IFS= read -r in ; do chmod 755 "$in";done <file.txt
当您使用STDIN
读取file.txt
时,您的脚本不能交互式(您不能再使用STDIN
)。
3。 while read -u
,使用专用fd
。
语法:while read ...;done <file.txt
将 STDIN
重定向到 file.txt
。这意味着,在他们完成之前,您将无法处理流程。
如果你打算创建交互式工具,你必须避免使用STDIN
,而使用一些替代的文件描述符。 p>
常量文件描述符是:0
代表 STDIN,1
代表 STDOUT 和 2
代表 STDERR。您可以通过以下方式查看它们:
ls -l /dev/fd/
或
ls -l /proc/self/fd/
从那里,您必须在0
和63
之间选择未使用的数字(实际上,更多,取决于sysctl
超级用户工具)作为文件描述符:
对于这个演示,我将使用 fd 7
:
exec 7<file.txt # Without spaces between `7` and `<`!
ls -l /dev/fd/
那么你可以这样使用read -u 7
:
while read -u 7 filename; do
ans=
while [ -z "$ans" ]; do
read -p "Process file '$filename' (y/n)? " -sn1 foo
[ "$foo" ] && [ -z "$foo/[yn]" ] && ans=$foo || echo '??'
done
if [ "$ans" = "y" ]; then
echo Yes
echo "Processing '$filename'."
else
echo No
fi
done 7<file.txt
done
关闭fd/7
:
exec 7<&- # This will close file descriptor 7.
ls -l /dev/fd/
注意:我让 striked 版本,因为这种语法可能很有用,当使用并行进程进行许多 I/O 时:
mkfifo sshfifo
exec 7> >(ssh -t user@host sh >sshfifo)
exec 6<sshfifo
【讨论】:
由于xargs
最初是为了满足这种需求而构建的,所以一些功能,如 在当前环境中尽可能长时间地构建命令 用于在此调用 chmod
case 越少越好,减少 forks 确保效率。 while ;do..done <$file
暗示为 1 个文件运行 1 个分叉。 xargs
可以为一千个文件运行 1 个 fork……以可靠的方式。
为什么第三个命令在 makefile 中不起作用?我收到“意外标记 `
这似乎与 Makefile 特定的语法有关。您可以尝试反转命令行:cat file.txt | tr \\n \\0 | xargs -0 -n1 chmod 755
@F.Hauri 出于某种原因,tr \\n \\0 <file.txt |xargs -0 [command]
比您描述的方法快约 50%。
2019 年 10 月,新编辑,添加 interactive 文件处理器示例。【参考方案2】:
是的。
while read in; do chmod 755 "$in"; done < file.txt
这样可以避免cat
进程。
cat
对于这样的目的几乎总是不好的。你可以阅读更多关于 Useless Use of Cat.
【讨论】:
避免 onecat
是个好主意,但在这种情况下, 指示的命令是 xargs
那个链接好像不相关,可能是网页内容变了?不过,其余的答案很棒:)
@starbeamrainbowlabs 是的。页面似乎已被移动。我已经重新链接,现在应该可以了。谢谢:)
谢谢!这很有帮助,尤其是当您需要做其他事情而不是调用 chmod
时(即真正为文件中的每一行运行一个命令)。
虽然这可能更直观,但处理文本的 shell 循环是 dramatically slow and bad practice。我刚刚测量了一个示例文件的回显:与接受的答案相比,这慢了 18 倍。【参考方案3】:
如果你有一个不错的选择器(例如目录中的所有 .txt 文件) 你可以这样做:
for i in *.txt; do chmod 755 "$i"; done
bash for loop
或您的变体:
while read line; do chmod 755 "$line"; done < file.txt
【讨论】:
行不通的是,如果行中有空格,输入是按空格而不是按行分割的。 @Michael Fox :可以通过更改分隔符来支持带空格的行。要将其更改为换行符,请在脚本/命令之前设置“IFS”环境变量。例如:export IFS='$\n' 我上一条评论中的错字。应该是:export IFS=$'\n'【参考方案4】:如果你想为每一行并行运行你的命令,你可以使用GNU Parallel
parallel -a <your file> <program>
文件的每一行都将作为参数传递给程序。默认情况下,parallel
运行的线程数量与您的 CPU 数量一样多。但是你可以用-j
指定它
【讨论】:
【参考方案5】:如果您知道输入中没有任何空格:
xargs chmod 755 < file.txt
如果路径中可能有空格,并且您有 GNU xargs:
tr '\n' '\0' < file.txt | xargs -0 chmod 755
【讨论】:
我知道 xargs,但(遗憾的是)它似乎不如 bash 内置功能(如 while 和 read)可靠。另外,我没有 GNU xargs,但我使用的是 OS X,xargs 在这里也有一个 -0 选项。感谢您的回答。 @hawk No:xargs
是健壮的。这个工具很老了,他的代码被强烈revisited。他的目标最初是根据 shell 限制(64kchar/line 或类似的东西)构建行。现在这个工具可以处理非常大的文件,并且可以减少很多 fork 到最终命令的数量。请参阅my answer 和/或man xargs
。
@hawk 以哪种方式不太可靠的解决方案?如果它可以在 Linux、Mac/BSD 和 Windows 中运行(是的,MSYSGIT 的捆绑包 GNU xargs),那么它就非常可靠。
对于那些仍然从搜索结果中找到它的人...您可以使用 Homebrew (brew install findutils
) 在 macOS 上安装 GNU xargs,然后使用 gxargs
调用 GNU xargs,例如gxargs chmod 755 < file.txt
【参考方案6】:
您也可以使用 AWK,它可以让您更灵活地处理文件
awk ' print "chmod 755 "$0"" | "/bin/sh"' file.txt
如果您的文件有如下字段分隔符:
字段1,字段2,字段3
只获取您所做的第一个字段
awk -F, ' print "chmod 755 "$1"" | "/bin/sh"' file.txt
您可以在 GNU 文档中查看更多详细信息 https://www.gnu.org/software/gawk/manual/html_node/Very-Simple.html#Very-Simple
【讨论】:
【参考方案7】:我看到你标记了 bash,但 Perl 也是一个很好的方法:
perl -p -e '`chmod 755 $_`' file.txt
您还可以应用正则表达式来确保获得正确的文件,例如只处理 .txt 文件:
perl -p -e 'if(/\.txt$/) `chmod 755 $_`' file.txt
要“预览”正在发生的事情,只需将反引号替换为双引号并添加 print
:
perl -p -e 'if(/\.txt$/) print "chmod 755 $_"' file.txt
【讨论】:
为什么要使用反引号? Perl 有一个chmod
function
你会想要perl -lpe 'chmod 0755, $_' file.txt
-- 使用-l
来实现“auto-chomp”功能【参考方案8】:
现在xargs
仍然是这个问题的答案,但是...您现在可以使用-a
选项直接从文件中读取输入:
xargs -a file.txt -n 1 -I chmod 775
【讨论】:
这是唯一对我有用的答案,谢谢。不敢相信有人写了 500 行只是为了一个无用的答案【参考方案9】:该逻辑适用于许多其他目标。 以及如何从 /home/ 文件系统中读取每个用户的 .sh_history ?如果有上千个呢?
#!/bin/ksh
last |head -10|awk 'print $1'|
while IFS= read -r line
do
su - "$line" -c 'tail .sh_history'
done
这里是脚本https://github.com/imvieira/SysAdmin_DevOps_Scripts/blob/master/get_and_run.sh
【讨论】:
【参考方案10】:我知道已经晚了,但还是
如果您碰巧遇到带有\r\n
而不是\n
的Windows 保存文本文件,如果您的命令在读取行之后有某事作为参数,您可能会对输出感到困惑。所以请删除\r
,例如:
cat file | tr -d '\r' | xargs -L 1 -i echo do_sth_with__as_line
【讨论】:
以上是关于如何为文件的每一行运行命令?的主要内容,如果未能解决你的问题,请参考以下文章
Bazel + C++ 项目:如何为“运行”命令指定工作目录