ファイルの各行に対してコマンドをどのように実行しますか?


161

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

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

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

回答:


127

ファイルを1行ずつ読み取り、コマンドを実行します。4つの回答

これは、答えが1つだけではないためです...

  1. shell コマンドライン展開
  2. xargs 専用ツール
  3. while read いくつかのコメント付き
  4. while read -uインタラクティブ処理のfdために専用のを使用(サンプル)

OPリクエストについて:fileリストされているすべてのターゲットで実行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..
    

    コマンドは、1行に1回実行されます。

  3. while read とバリアント。

    OPが機能することを示しcat file.txt | while read in; do chmod 755 "$in"; doneますが、2つの問題があります。

    • cat |ある無用フォーク、と

    • | while ... ;doneなりますサブシェル環境は後disapearます;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のためのSTDOUTおよび2ため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、できるだけ少ないこの場合、還元フォークが efficienceを確保します。while ;do..done <$file1つのファイルに対して1つのフォークを実行することを暗黙指定します。xargs1000のファイルに対して1フォークを実行できます...信頼できる方法で。
F.ハウリ

1
makefileで3番目のコマンドが機能しないのはなぜですか?「予期しないトークン `<'の近くで構文エラーが発生しますが、コマンドラインから直接実行すると機能します。
Woodrow Barlow

2
これはMakefile固有の構文にリンクされているようです。コマンドラインを逆転しようとすることができます:cat file.txt | tr \\n \\0 | xargs -0 -n1 chmod 755
F. HAURI

@ 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ほとんどの場合、このような目的には適していません。あなたは猫の無用の使用についてもっと読むことができます


避けてください1は cat良いアイデアですが、この場合には、指示されたコマンドがあるxargs
F. HAURI

そのリンクは関連性がないようです。おそらくWebページのコンテンツが変更されていますか?残りの答えは素晴らしいです:)
starbeamrainbowlabs '21 / 04/15

@starbeamrainbowlabsはい。ページが移動したようです。私は再リンクしており、今は大丈夫です。ありがとう:)
PP

1
ありがとう!これは特に、呼び出し以外のことを行う必要がある場合chmod(つまり、実際にはファイルの各行に対して1つのコマンドを実行する場合)に役立ちました。
Lundbergによる

バックスラッシュに注意してください!from unix.stackexchange.com/a/7561/28160- " read -r標準入力から1行を読み取ります(バックスラッシュread-r解釈しないと、必要ありません)。"
ブラジル人のガイ

16

素敵なセレクターがある場合(たとえば、dir内のすべての.txtファイル)、次のようにできます。

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

ループのbash

またはあなたの変種:

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

機能しないのは、行にスペースがある場合、入力が行ではなくスペースで分割されることです。
マイケルフォックス

@Michael Fox:スペースを含む行は、セパレーターを変更することでサポートできます。これを改行に変更するには、スクリプト/コマンドの前に「IFS」環境変数を設定します。例:export IFS = '$ \ n'
codesiffer

私の最後のコメントのタイプミス。する必要があります:export IFS = $ '\ n'
codesniffer

14

入力に空白がないことがわかっている場合:

xargs chmod 755 < file.txt

パスに空白がある可能性があり、GNU xargsがある場合:

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

私はxargsについて知っていますが、(悲しいことに)whileやreadのようなbash組み込み機能よりも信頼できる解決策ではないようです。また、私はGNU xargsを持っていませんが、OS Xを使用しており、xargsにも-0オプションがあります。答えてくれてありがとう。
hawk

1
@hawkいいえ:xargs堅牢です。このツールは非常に古く、彼のコードは強く再考されています。彼の目標は、シェルの制限(64kchar / lineなど)に関してラインを構築することでした。これで、このツールは非常に大きなファイルを処理できるようになり、最終的なコマンドへの分岐の数を大幅に減らすことができます。私の答えやを参照しくださいman xargs
F.ハウリ

@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

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

1
あなたがしたいperl -lpe 'chmod 0755, $_' file.txt- -l「オートチョップ」機能に使用する
グレン・ジャックマン

1

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で詳細を確認でき ます。



0

遅いけどまだ

万が一、の\r\n代わりにで保存されたテキストファイルをウィンドウに実行した\n場合、コマンドが読み取り行の後に引数としてsthを持っていると、出力が混乱する可能性があります。\rたとえば、を削除してください。

cat file | tr -d '\r' | xargs -L 1 -i echo do_sth_with_{}_as_line
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.