如何为文件的每一行运行命令(例如chmod) ?

例如,现在我正在使用以下命令来更改几个文件,这些文件的Unix路径是我写到一个文件中的:

cat file.txt | while read in; do chmod 755 "$in"; done

有没有更优雅、更安全的方式?

265978 次浏览

是的。

while read in; do chmod 755 "$in"; done < file.txt

这样你可以避免cat进程。

cat对于这样的目的几乎总是不好的。你可以阅读更多关于无用的使用猫

如果你有一个好的选择器(例如所有的。txt文件在一个目录) 你可以这样做:

for i in *.txt; do chmod 755 "$i"; done

bash for循环

或者是你的变种:

while read line; do chmod 755 "$line"; done < file.txt

如果你知道你在输入中没有任何空白:

xargs chmod 755 < file.txt

如果路径中可能有空格,如果你有GNU xargs:

tr '\n' '\0' < file.txt | xargs -0 chmod 755

我看到你标记了bash,但Perl也是一种很好的方式:

perl -p -e '`chmod 755 $_`' file.txt

你也可以应用一个regex来确保你得到了正确的文件,例如只处理。txt文件:

perl -p -e 'if(/\.txt$/) `chmod 755 $_`' file.txt

要“预览”正在发生的事情,只需将反撇号替换为双引号,并在print前加引号:

perl -p -e 'if(/\.txt$/) print "chmod 755 $_"' file.txt

逐行读取文件并执行命令:4+答案

因为(和/或)的主要用途是运行其他命令,所以答案不止一个!!

< p > 0。Shell命令行扩展
1。xargs专用工具
2。while read with some备注
3所示。while read -u使用专用的fd,用于互动处理(sample)
5。运行与内联生成脚本

对于OP请求:在文件中列出的所有目标上运行chmodxargs是指定的工具。但对于其他一些应用程序,少量的文件,等等…

0. 读取整个文件作为命令行参数。

如果你的文件不是太大,并且所有文件都是好名字(没有空格或其他特殊字符,如引号),你可以使用shell 命令行扩展。简单:

chmod 755 $(<file.txt)

对于少量的文件(行),这个命令是较轻的一个。

1. xargs是正确的工具

对于更大数量的文件,或几乎任何行数的输入文件…

对于许多binutils工具,如chownchmodrmcp -t

xargs chmod 755 <file.txt

可以通过find发现文件后面使用:

find /some/path -type f -uid 1234 -print | xargs chmod 755

如果你在file.txt中有特殊字符和/或很多行。

xargs -0 chmod 755 < <(tr \\n \\0 <file.txt)


find /some/path -type f -uid 1234 -print0 | xargs -0 chmod 755

如果您的命令需要为每个条目运行1次:

xargs -0 -n 1 chmod 755 < <(tr \\n \\0 <file.txt)

这个示例不需要这样做,因为chmod接受多个文件作为参数,但这与问题的标题相匹配。

对于某些特殊情况,你甚至可以在xargs生成的命令中定义file参数的位置:

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 |是一个无用的叉,和

  • | while ... ;done将成为亚层,其环境将在;done之后消失。

所以这样写更好:

while read in; do
chmod 755 "$in"
done < file.txt

  • 你可能会被警告$IFSread标志:

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...

在某些情况下,您可能需要使用

while IFS= read -r in;do
chmod 755 "$in"
done <file.txt

用来避免奇怪文件名的问题。如果你遇到UTF-8的问题:

while LANG=C IFS= read -r in ; do
chmod 755 "$in"
done <file.txt

当您使用从标准输入__abc0file .txt '重定向时,您的脚本不能交互式地读取其他输入(您不能再将标准输入用于其他输入)。

3.while read,使用专用的fd

语法:while read ...;done <file.txt将标准输入重定向到来自file.txt。这意味着您将无法处理流程,直到它们完成。

这将允许你同时使用多个输入,你可以合并两个文件(像这里:scriptReplay.sh),或者:

如果你计划创建一个互动工具,你必须避免使用标准输入,并使用一些替代的文件描述符。

常量文件描述符是:

  • 0为标准输入
  • 1为标准输出
  • 2为标准误差。

3.1先

你可以通过以下途径看到它们:

ls -l /dev/fd/

ls -l /proc/$$/fd/

从那里,你必须选择0到63之间的未使用的数字(实际上,更多,取决于sysctl超级用户工具)作为你的文件描述符。

对于这个演示,我将使用文件描述符7:

while read <&7 filename; do
ans=
while [ -z "$ans" ]; do
read -p "Process file '$filename' (y/n)? " 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

如果你想在更多不同的步骤中读取输入文件,你必须使用:

exec 7<file.txt      # Without spaces between `7` and `<`!
# ls -l /dev/fd/


read <&7 headLine
while read <&7 filename; do
case "$filename" in
*'----' ) break ;;  # break loop when line end with four dashes.
esac
....
done


read <&7 lastLine


exec 7<&-            # This will close file descriptor 7.
# ls -l /dev/fd/

3.2 下相同

下,你可以让他为你选择任意的免费fd并存储到一个变量中:
exec {varname}</path/to/input:

while read -ru ${fle} filename;do
ans=
while [ -z "$ans" ]; do
read -rp "Process file '$filename' (y/n)? " -sn 1 foo
[ "$foo" ] && [ -z "${foo/[yn]}" ] && ans=$foo || echo '??'
done
if [ "$ans" = "y" ]; then
echo Yes
echo "Processing '$filename'."
else
echo No
fi
done {fle}<file.txt

exec {fle}<file.txt
# ls -l /dev/fd/
read -ru ${fle} headline


while read -ru ${fle} filename;do
[[ -n "$filename" ]] && [[ -z ${filename//*----} ]] && break
....
done


read -ru ${fle} lastLine


exec {fle}<&-
# ls -l /dev/fd/

5. 为创建命令过滤输入文件

sed <file.txt 's/.*/chmod 755 "&"/' | sh

这不会优化,但对于更复杂(或条件)的操作可能有用:

sed <file.txt 's/.*/if [ -e "&" ];then chmod 755 "&";fi/' | sh


sed 's/.*/[ -f "&" ] \&\& echo "Processing: \\"&\\"" \&\& chmod 755 "&"/' \
file.txt | sh

如果你想对每一行并行运行命令,你可以使用GNU平行

parallel -a <your file> <program>

文件的每一行都将作为参数传递给程序。默认情况下,parallel运行与cpu数量相同的线程。但是你可以用-j来指定它

您还可以使用AWK,它可以为您提供更大的灵活性来处理文件

awk '{ print "chmod 755 "$0"" | "/bin/sh"}' file.txt

如果你的文件有一个字段分隔符:

field1, field2, field3

只得到第一个字段

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 < / p >

这个逻辑也适用于许多其他目标。 如何从/home/文件系统中读取每个用户的.sh_history ?如果有上千个呢?< / p >
#!/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

现在天xargs仍然是这个问题的答案,但是…你现在可以使用-a选项直接从文件中读取输入:

xargs -a file.txt -n 1 -I {} chmod 775 {}