如何为文件的每一行运行命令?


161

例如,现在我正在使用以下内容来更改我写入其Unix路径的几个文件:

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

有没有更优雅,更安全的方法?

Answers:


127

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

这是因为不仅只有一个答案...

  1. shell 命令行扩展
  2. xargs 专用工具
  3. while read 有一些评论
  4. while read -u使用private fd进行交互式处理(示例)

关于OP要求:运行chmod在文件中列出的所有目标xargs是指定的工具。但是对于其他一些应用程序,文件数量很少等。

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

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

    chmod 755 $(<file.txt)

    对于少量文件(行),此命令较轻。

  2. xargs 是正确的工具

    对于更大数量的文件,或输入文件中几乎任意数量的行...

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

    xargs chmod 755 <file.txt

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

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

    如果您的命令需要通过输入精确运行1次:

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

    每行指挥官一次

  3. 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
    • 当您STDIN用于阅读时file.txt,您的脚本无法进行交互(无法再使用STDIN)。

  4. while read -u,使用专用fd

    语法:while read ...;done <file.txt将重定向STDINfile.txt。这意味着,您将无法处理流程,直到流程完成。

    如果计划创建交互式工具,则必须避免使用,STDIN而应使用其他替代文件描述符

    常量文件描述符为:0用于STDIN1用于STDOUT2用于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/

    注意:我使用标准版本,因为在使用并行过程执行许多I / O时,此语法可能很有用:

    mkfifo sshfifo
    exec 7> >(ssh -t user@host sh >sshfifo)
    exec 6<sshfifo

3
正如xargs最初为满足这种需求而构建时一样,某些功能(例如,在当前环境中尽可能长地构建命令以chmod在这种情况下尽可能少地调用),减少派生确保了效率。while ;do..done <$file暗示对1个文件运行1个fork。xargs可以以一种可靠的方式运行一千个文件的fork。
F. Hauri

1
为什么第三个命令在Makefile中不起作用?我收到“意外令牌'<'附近的语法错误”,但直接从命令行执行即可。
Woodrow Barlow

2
这似乎与Makefile特定的语法有关。您可以尝试反转命令行:cat file.txt | tr \\n \\0 | xargs -0 -n1 chmod 755
F. Hauri 2015年

由于某种原因,@ F.Hauri tr \\n \\0 <file.txt |xargs -0 [command]比您描述的方法快约50%。
phil294

2019年10月,新编辑,添加了交互式文件处理器示例。
F.豪里

150

是。

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

这样,您可以避免一个cat过程。

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


避免一个 cat是一个好主意,但在这种情况下,指定的命令是xargs
F. Hauri

该链接似乎无关紧要,也许网页的内容已更改?其余的答案虽然很棒:)
starbeamrainbowlabs 2015年

@starbeamrainbowlabs是的。页面似乎已被移动。我已重新关联,现在应该可以了。谢谢:)
PP

1
谢谢!这很有用,特别是当您需要执行除调用之外的其他操作时chmod(即,实际上为文件中的每一行运行一个命令)。
Per Lundberg

注意反斜杠!来自unix.stackexchange.com/a/7561/28160-read -r从标准输入中读取一行(read-r解释反斜线,您不希望这样)。”
那位巴西人

16

如果您有一个不错的选择器(例如,目录中的所有.txt文件),则可以执行以下操作:

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

重击循环

或您的变体:

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

不起作用的是,如果行中有空格,则输入将被空格而不是行分开。
迈克尔·福克斯

@Michael Fox:可以通过更改分隔符来支持带空格的行。要将其更改为换行符,请在脚本/命令之前设置“ IFS”环境变量。例如:export IFS ='$ \
n'– codeniffer

我最近的评论中有错字。应该是:IFS = $ '\ n'出口
codesniffer

14

如果您知道输入中没有空格,请执行以下操作:

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选项。感谢你的回答。

1
@hawk否:xargs健壮。这个工具很老,他的代码也被重新审视。他的目标是最初建立关于shell限制的行(64kchar /行或诸如此类)。现在,此工具可以处理非常大的文件,并且可以减少到最终命令的派生次数。查看我的答案和/或man xargs
F. Hauri 2013年

@hawk哪种方法更可靠?如果它可以在Linux,Mac / BSD和Windows上运行(是的,MSYSGIT的GNU xargs捆绑包),那么它就已经可靠了。
卡米洛·马丁

1
对于仍然可以从搜索结果中找到这些内容的人……您可以使用Homebrew(brew install findutils)在macOS上安装GNU xargs ,然后调用GNU xargs gxargs,例如gxargs chmod 755 < file.txt
Jase

13

如果要为每行并行运行命令,则可以使用GNU Parallel

parallel -a <your file> <program>

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


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

2
为什么要使用反引号?Perl具有chmod功能
glenn jackman 2012年

1
您想要perl -lpe 'chmod 0755, $_' file.txt- -l用于“自动
匹配



0

我知道已经很晚了

如果您碰巧碰到了Windows保存的文本文件\r\n而不是\n,如果您的命令在读取行之后有某条命令作为参数,那么您可能会对输出感到困惑。因此,请删除\r,例如:

cat file | tr -d '\r' | xargs -L 1 -i echo do_sth_with_{}_as_line
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.