217

たとえば、現在、次を使用して、Unix パスをファイルに書き込んだいくつかのファイルを変更しています。

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

よりエレガントで安全な方法はありますか?

4

10 に答える 10

201

ファイルを 1 行ずつ読み取り、コマンドを実行する: 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..

commande は1 行に 1 回実行されます。

2.while readおよびバリアント。

OPが示唆cat file.txt | while read in; do chmod 755 "$in"; doneするように機能しますが、2つの問題があります:

 - `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ん)。

3. while read -u、専用を使用fd

構文:while read ...;done <file.txtにリダイレクトSTDINfile.txtます。つまり、プロセスが完了するまで、プロセスに対処することはできません。

対話型ツールを作成する予定がある場合はSTDIN、使用を避け、別のファイル記述子を使用する必要があります。

定数ファイル記述子0STDIN1用、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
于 2012-12-18T20:46:22.547 に答える
176

はい。

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

これにより、プロセスを回避できcatます。

catほとんどの場合、このような目的には適していません。猫の無用な使用について詳しく読むことができます。

于 2012-12-18T18:19:09.467 に答える
21

素敵なセレクター (たとえば、ディレクトリ内のすべての .txt ファイル) がある場合は、次のことができます。

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

ループのためのバッシュ

またはあなたの変種:

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

コマンドを各行で並行して実行したい場合は、GNU Parallelを使用できます。

parallel -a <your file> <program>

ファイルの各行は引数として 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 に答える
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 に答える