如何在文件的每一行上运行命令,例如chmod?

256
例如,目前我正在使用以下代码来更改几个文件的Unix路径并将其写入一个文件:
cat file.txt | while read in; do chmod 755 "$in"; done

是否有一种更加优雅、更安全的方法?

9个回答

251

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

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

0. Shell命令行扩展
1. xargs专用工具
2. while read带有一些备注
3. while read -u使用专用的fd,用于交互式处理(示例)
5. 使用内联生成的脚本运行

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

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

如果

  • 你的文件不会太大(在我的主机上测试了128Mb的文件,有超过10,000,000行)
  • 所有文件的命名都很好(没有空格或其他特殊字符,如引号)

你可以使用shell命令行扩展。简单来说:

chmod 755 $(<file.txt)

这个命令是更简单的一个。
2. xargs 是正确的工具
对于
- 更多的文件,或者几乎任意数量的输入文件行... - 文件名可能包含空格或特殊字符的文件
对于许多 binutils 工具,比如 chown、chmod、rm、cp -t ...
xargs chmod 755 <file.txt

可以在find命令的找到的文件后面使用pipe

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

如果您的命令需要针对每个条目仅运行一次:
xargs -0 -n 1 chmod 755 < <(tr \\n \\0 <file.txt)

这对于这个示例来说是不需要的,因为chmod可以接受多个文件作为参数,但这与问题的标题相匹配。
对于一些特殊情况,你甚至可以在由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..

每行执行一次您的命令。

第三章之前的重要前言。

下进行循环通常是一个坏主意!有很多关于在shell下进行循环的警告!

在进行循环之前,请考虑并行化专用工具

您可以使用与和管理专用工具进行交互。一些示例:

3. while read和其它变体。

为此,请确保文件以换行符结尾。

如OP所建议,

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

工作没问题,但有两个问题:
1. `cat |` 是一个无用的分叉, 2. `| while ... ;done` 将成为一个子shell,其环境将在 `;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

当你使用标准输入重定向`for reading` file.txt时,你的脚本无法交互地读取其他输入(你不能再使用标准输入来读取其他输入)。
4. `while read`,使用专用的`fd`。
语法:`while read ...;done
这将允许你同时使用多个输入,你可以合并两个文件(就像这里:scriptReplay.sh),或者可能是:
你计划创建一个交互式工具,你必须避免使用标准输入,并使用一些替代的文件描述符。
常量文件描述符为:
0表示标准输入 1表示标准输出 2表示标准错误。

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

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

这不会优化forks,但对于更复杂(或有条件的)操作可能会有用。
sed <file.txt 's/.*/if [ -e "&" ];then chmod 755 "&";fi/' | sh

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

这在输入为“feed”而不是“file”的情况下非常有用。实际示例:使用rsync日志输出作为sed输入,以便在删除对应的描述文件时删除项目文件。请参见我的回答,如果在另一个目录中不存在同名但扩展名不同的文件,则删除文件,这与SO提问者的预期有很大不同。

3
由于xargs最初就是为了满足这种需求而构建的,所以它具有一些特性,比如在当前环境下尽可能长地构建命令来调用chmod,以减少执行次数并降低forks以确保效率。使用while ;do..done <$file会为每个文件运行1个fork,而xargs可以以可靠的方式为数千个文件运行1个fork...。 - F. Hauri - Give Up GitHub
1
为什么在Makefile中第三个命令无法工作?我得到了“syntax error near unexpected token `<’” 的错误,但是直接从命令行执行却可以正常工作。 - Woodrow Barlow
2
这似乎与Makefile特定的语法有关。您可以尝试反转命令行:cat file.txt | tr \\n \\0 | xargs -0 -n1 chmod 755 - F. Hauri - Give Up GitHub
@F.Hauri,由于某种原因,“tr \n \0 <file.txt |xargs -0 [command]”比您描述的方法快约50%。 - phil294
1
@tripleee 答案已编辑... - F. Hauri - Give Up GitHub
显示剩余7条评论

198

是的。

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

这种方法可以避免使用 cat 命令。

cat 命令几乎总是不适合此类用途。您可以阅读有关“无用的Cat使用”的更多信息。


避免使用cat是一个好主意,但在这种情况下,所指的命令是xargs - F. Hauri - Give Up GitHub
那个链接似乎不相关,也许网页的内容已经改变了?不过答案的其余部分非常棒 :) - starbeamrainbowlabs
@starbeamrainbowlabs 是的。看起来页面已经被移动了。我已经重新链接,现在应该没问题了。谢谢 :) - P.P
1
谢谢!这很有帮助,特别是当你需要做一些不同于调用chmod的事情时(例如,对文件中的每一行运行一个命令)。 - Per Lundberg
小心反斜杠!从http://unix.stackexchange.com/a/7561/28160 - “read -r从标准输入读取单行(没有-rread会解释反斜杠,你不想要那个)。” - That Brazilian Guy
1
虽然这种方法可能更直观,但使用shell循环处理文本会非常缓慢且是不良实践。我刚刚测试了一下回显一个示例文件:与被接受的答案相比,这种方法慢了18倍。 - phil294

24

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

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

Bash循环语句

或者您自己的变体:

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

2
不起作用的是,如果行中有空格,则输入会按空格而不是按行分割。 - Michael Fox
@Michael Fox:带有空格的行可以通过更改分隔符来支持。在运行脚本/命令之前,设置“IFS”环境变量将其更改为换行符。例如:export IFS='$\n' - codesniffer
我上一条评论中打错了,应该是:export IFS=$'\n'。 - codesniffer
@codesniffer 这里不需要使用 export。它的作用是使变量对子进程可见(因此,如果您想在从当前进程启动的子shell中更改分隔符,则很有用,但在这里并不真正相关或有用)。 - tripleee

19

如果你希望针对每一行并行运行命令,你可以使用GNU Parallel

parallel -a <your file> <program>

您的文件中的每一行将作为程序的参数传递。默认情况下,parallel 将运行与您的 CPU 核心数相同的线程。但您可以使用 -j 参数来指定线程数量。


16

如果您知道输入中没有任何空格:

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
1
@hawk 不是的:xargs 是非常强大的工具。这个工具非常古老,它的代码已经被重新审查过了。它的目标最初是为了在遵守 shell 限制(64kchar/line 或者其他)的情况下构建行。现在这个工具可以处理非常大的文件,并且可以大大减少到最终命令的 fork 数量。请参考我的回答和/或 man xargs - F. Hauri - Give Up GitHub
@hawk 在哪方面不太可靠?如果它在Linux、Mac/BSD和Windows(是的,MSYSGIT捆绑了GNU xargs)上都可以工作,那么它就是最可靠的。 - Camilo Martin
1
对于那些仍然从搜索结果中找到这篇文章的人...你可以使用Homebrew(brew install findutils)在macOS上安装GNU xargs,然后用gxargs代替GNU xargs,例如:gxargs chmod 755 < file.txt - Jase
xargs 本身很强大,但您必须了解它如何处理(或无法处理)输入中的引号等内容。使用 xargs -0 的解决方法完全可预测和强大,但遗憾的是仅适用于 GNU xargs - tripleee

6

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

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


1
这是唯一对我有用的答案,谢谢。真不敢相信有人为了一个无用的答案写了500行代码。 - Mdev
提醒一下,如果有人遇到这个问题,macOS自带的xargs版本不支持-a参数。 - OneHoopyFrood
xargs -a 是GNU扩展的一部分,这意味着它通常可以在Linux上直接使用,但在其他地方可能需要单独安装许多常见实用程序的GNU版本。从标准输入读取文件名的标准解决方案仍然可以在GNU和其他版本的xargs中可移植地使用。 - tripleee

5
您还可以使用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

3

我看到您标记了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

3
为什么要使用反引号?Perl有一个chmod函数 - glenn jackman
1
您可能需要使用 perl -lpe 'chmod 0755, $_' file.txt 命令——使用 -l 来启用“自动去除换行符”功能。 - glenn jackman

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接