【发布时间】:2012-12-06 00:25:02
【问题描述】:
例如,现在我正在使用以下内容来更改我将其 Unix 路径写入文件的几个文件:
cat file.txt | while read in; do chmod 755 "$in"; done
有没有更优雅、更安全的方法?
【问题讨论】:
例如,现在我正在使用以下内容来更改我将其 Unix 路径写入文件的几个文件:
cat file.txt | while read in; do chmod 755 "$in"; done
有没有更优雅、更安全的方法?
【问题讨论】:
这是因为不仅有 1 个答案...
shell命令行扩展xargs专用工具while read 附一些评论while read -u 使用专用fd,用于交互式处理(示例)关于 OP 请求:在文件中列出的所有目标上运行 chmod,xargs 是指定的工具。但是对于其他一些应用程序,少量文件等......
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.
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..
命令在哪里完成每行一次。
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)。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……以可靠的方式。
cat file.txt | tr \\n \\0 | xargs -0 -n1 chmod 755
tr \\n \\0 <file.txt |xargs -0 [command] 比您描述的方法快约 50%。
是的。
while read in; do chmod 755 "$in"; done < file.txt
这样可以避免cat 进程。
cat 对于这样的目的几乎总是不好的。你可以阅读更多关于 Useless Use of Cat.
【讨论】:
cat 是个好主意,但在这种情况下, 指示的命令是 xargs
chmod 时(即真正为文件中的每一行运行一个命令)。
如果你有一个不错的选择器(例如目录中的所有 .txt 文件) 你可以这样做:
for i in *.txt; do chmod 755 "$i"; done
或您的变体:
while read line; do chmod 755 "$line"; done < file.txt
【讨论】:
如果你想为每一行并行运行你的命令,你可以使用GNU Parallel
parallel -a <your file> <program>
文件的每一行都将作为参数传递给程序。默认情况下,parallel 运行的线程数量与您的 CPU 数量一样多。但是你可以用-j指定它
【讨论】:
如果您知道输入中没有任何空格:
xargs chmod 755 < file.txt
如果路径中可能有空格,并且您有 GNU xargs:
tr '\n' '\0' < file.txt | xargs -0 chmod 755
【讨论】:
xargs 是健壮的。这个工具很老了,他的代码被强烈revisited。他的目标最初是根据 shell 限制(64kchar/line 或类似的东西)构建行。现在这个工具可以处理非常大的文件,并且可以减少很多 fork 到最终命令的数量。请参阅my answer 和/或man xargs。
brew install findutils) 在 macOS 上安装 GNU xargs,然后使用 gxargs 调用 GNU xargs,例如gxargs chmod 755 < file.txt
您也可以使用 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
【讨论】:
我看到你标记了 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
【讨论】:
chmod function
perl -lpe 'chmod 0755, $_' file.txt -- 使用-l 来实现“auto-chomp”功能
现在xargs 仍然是这个问题的答案,但是...您现在可以使用-a 选项直接从文件中读取输入:
xargs -a file.txt -n 1 -I {} chmod 775 {}
【讨论】:
该逻辑适用于许多其他目标。 以及如何从 /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
【讨论】:
我知道已经晚了,但还是
如果您碰巧遇到带有\r\n 而不是\n 的Windows 保存文本文件,如果您的命令在读取行之后有某事作为参数,您可能会对输出感到困惑。所以请删除\r,例如:
cat file | tr -d '\r' | xargs -L 1 -i echo do_sth_with_{}_as_line
【讨论】: