217

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

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

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

4

10 回答 10

201

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

这是因为不仅有1个答案...

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

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

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

但,

  • 您可能会收到以下警告$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...
   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将重定向STDINfile.txt. 这意味着,在他们完成之前,您将无法处理流程。

如果您打算创建交互式工具,则必须避免使用STDIN并使用一些替代文件描述符

常量文件描述符是:0对于STDIN1对于STDOUT2对于STDERR。您可以通过以下方式查看它们:

    ls -l /dev/fd/

或者

    ls -l /proc/self/fd/

从那里,您必须选择未使用的数字,介于063(实际上,更多,实际上取决于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
于 2012-12-18T20:46:22.547 回答
176

是的。

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

这样你就可以避免一个cat过程。

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

于 2012-12-18T18:19:09.467 回答
21

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

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

bash for 循环

或您的变体:

while read line; do chmod 755 "$line"; done < file.txt
于 2012-12-18T18:31:43.827 回答
17

如果你想为每一行并行运行你的命令,你可以使用GNU Parallel

parallel -a <your file> <program>

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

于 2016-03-21T18:02:10.020 回答
15

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

xargs chmod 755 < file.txt

如果路径中可能有空格,并且您有 GNU xargs:

tr '\n' '\0' < file.txt | xargs -0 chmod 755
于 2012-12-18T18:49:20.060 回答
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

于 2018-07-11T19:01:29.490 回答
4

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

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

于 2021-03-18T16:19:48.673 回答
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
于 2012-12-18T18:49:37.860 回答
0

该逻辑适用于许多其他目标。以及如何从 /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

于 2018-09-30T16:41:28.103 回答
0

我知道已经晚了,但仍然

如果您有任何机会遇到使用\r\n而不是 的Windows 保存的文本文件,\n如果您的命令在读取行之后有某事作为参数,您可能会对输出感到困惑。所以请删除\r,例如:

cat file | tr -d '\r' | xargs -L 1 -i echo do_sth_with_{}_as_line
于 2020-02-20T09:43:57.937 回答