ディレクトリ内のすべてのファイルに対してコマンドを実行します


290

誰かが次のことを行うためのコードを提供してくれませんか?ファイルのディレクトリがあり、そのすべてをプログラムで実行する必要があるとします。プログラムは結果を標準出力に出力します。ディレクトリに移動し、各ファイルでコマンドを実行し、出力を1つの大きな出力ファイルに連結するスクリプトが必要です。

たとえば、1つのファイルに対してコマンドを実行するには:

$ cmd [option] [filename] > results.out

3
質問に追加したいと思います。xargsを使用して実行できますか?例ls <directory> | xargs cmd [options] {filenames put in here automatically by xargs} [more arguments] > results.out
Ozair Kafray

2
それはできますが、おそらくドライブに使用lsたくないでしょうxargscmdまったく上手に書かれていれば、たぶんあなたは単にそうすることができますcmd <wildcard>
tripleee 2017年

回答:


425

次のbashコードは$ fileをコマンドに渡し、$ fileは/ dir内のすべてのファイルを表します

for file in /dir/*
do
  cmd [option] "$file" >> results.out
done

el@defiant ~/foo $ touch foo.txt bar.txt baz.txt
el@defiant ~/foo $ for i in *.txt; do echo "hello $i"; done
hello bar.txt
hello baz.txt
hello foo.txt

23
にファイルが存在しない場合/dir/でも、ループは「*」の値で1回実行されますが$file、これは望ましくない場合があります。これを回避するには、ループの期間中nullglobを有効にします。ループの前にこの行を追加し、ループのshopt -s nullglob後にこの行を追加しますshopt -u nullglob #revert nullglob back to it's normal default state
Stew-au

43
+1、それは私に私の壁紙コレクション全体を費やすだけです。私の後の誰もが、二重引用符を使用します。"$ file"
Behrooz 2013

ループ内で出力ファイルが同じ場合、ループの外側にリダイレクトする方がはるかに効率的done >results.outです(おそらく、ここで想定しているように、追加する代わりに上書きすることができます)。
tripleee 2017年

入力ファイルにカスタム名が付けられた個々の結果ファイルをどのように取得しますか?
ティモシースワン

1
dir内の大量のファイルに対してこのコマンドを使用する場合は注意してください。代わりにfind -execを使用してください。
kolisko

181

これはどう:

find /some/directory -maxdepth 1 -type f -exec cmd option {} \; > results.out
  • -maxdepth 1引数は、findが再帰的にサブディレクトリに降りることを防ぎます。(そのようなネストされたディレクトリを処理させたい場合は、これを省略できます。)
  • -type -f プレーンファイルのみが処理されることを指定します。
  • -exec cmd option {}見つかったファイルごとにcmd指定さoptionれたファイル名で置き換えて実行するように指示します{}
  • \; コマンドの終わりを示します。
  • 最後に、すべての個々のcmd実行からの出力はにリダイレクトされます results.out

ただし、ファイルが処理される順序を気にする場合は、ループを作成した方がよい場合があります。私findはファイルをiノードの順序で処理すると思います(ただし、それについては間違っている可能性があります)。


1
これは、ファイルを処理する正しい方法です。forループを使用すると、多くの理由によりエラーが発生しやすくなります。また、statやなどの他のコマンドを使用して並べ替えを行うこともできますsort。これはもちろん、並べ替えの基準に依存します。
tuxdna 2013

1
2つのコマンドを実行したい場合、-execオプションの後にそれらをどのようにリンクしますか?それらを一重引用符で囲む必要がありますか?
フライ

find常に最良のオプションです。オプションを使用してファイル名パターンでフィルタリング-nameでき、単一のコマンドで実行できます。
ジョアン・ピメンテルフェレイラ

3
:@freiあなたの質問への答えはここにあるstackoverflow.com/a/6043896/1243247が、基本的には追加-execオプション:find . -name "*.txt" -exec echo {} \; -exec grep banana {} \;
ジョアン・ピメンテルフェレイラ

2
オプションとしてファイル名をどのように参照できますか?
Toskan

54

コマンドラインから次のコマンドを実行して、ラズベリーpiでこれを実行しています。

for i in *;do omxplayer "$i";done

7

受け入れられた/高い投票の答えは素晴らしいですが、いくつかの重要な詳細が欠けています。この投稿では、シェルのパス名の展開(glob)が失敗した場合、ファイル名に改行/ダッシュ記号が埋め込まれていて、結果をファイル。

実行しているときに使用したシェルグロブ拡張*が存在しない場合は展開が失敗する可能性がある何のディレクトリに存在するファイルと非拡大グロブ文字列は、望ましくない結果を持っている可能性があり、実行するコマンドに渡されます。bashシェルはこの使用のために拡張されたシェルのオプションを提供しますnullglob。したがって、ループは基本的にファイルを含むディレクトリ内で次のようになります

 shopt -s nullglob

 for file in ./*; do
     cmdToRun [option] -- "$file"
 done

これにより、式./*がファイルを返さない場合(ディレクトリが空の場合)にforループを安全に終了できます。

またはPOSIXに準拠した方法で(nullglobあるbash特定の)

 for file in ./*; do
     [ -f "$file" ] || continue
     cmdToRun [option] -- "$file"
 done

これにより、式が1回失敗したときにループ内に移動し、[ -f "$file" ]展開されていない文字列./*がそのディレクトリ内の有効なファイル名であるかどうかを条件チェックします。したがって、この条件が満たされない場合continueは、forループを再開して、その後は実行されないようにします。

また--、ファイル名引数を渡す直前の使用法にも注意してください。前述のように、シェルのファイル名にはファイル名のどこにでもダッシュを含めることができるため、これが必要です。一部のシェルコマンドはそれを解釈し、名前が適切に引用符で囲まれていない場合はコマンドオプションとして扱い、フラグが指定されているかどうかを考慮してコマンドを実行します。

--信号コマンドがコマンドフラグとしてではなくファイル名のみ、このポイントを超えて任意の文字列を解析するべきではない手段、その場合のコマンドラインオプションの終わり。


ファイル名を二重引用符で囲むと、名前にグロブ文字または空白が含まれる場合が正しく解決されます。ただし、* nixファイル名には改行を含めることもできます。したがって、有効なファイル名の一部にできない唯一の文字、つまりnullバイト(\0)でファイル名を区切ります。以来bash、内部で使用してCヌルバイトが文字列の終わりを示すために使用されているスタイルの文字列を、それがこのために右の候補です。

したがってprintf、シェルのオプションを使用して、このNULLバイトでファイルを区切る-dオプションを使用してreadコマンドは、以下のようにします

( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
    cmdToRun [option] -- "$file"
done

nullglobそしてprintf周りにラップさ(..)を避けるためにため、彼らは基本的に、サブシェル(子シェル)で実行されていることを意味するnullglobコマンドが終了後、親シェルに反映するオプションを選択します。コマンドの-d ''オプションはPOSIXに準拠していないため、これを実行するにはシェルが必要です。使用するreadbashfindコマンドすると、これは次のように行うことができます

while IFS= read -r -d '' file; do
    cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)

以下のためにfindサポートしない実装-print0(GNUとFreeBSDの実装以外)、これを使用してエミュレートすることができますprintf

find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --

もう1つの重要な修正は、リダイレクトをforループの外に移動して、ファイルI / Oの数を減らすことです。ループ内で使用する場合、シェルはforループの反復ごとに2回システムコールを実行する必要があります。1回はファイルを開くため、もう1回はファイルに関連するファイル記述子を閉じるためです。これは、大きな反復を実行するためのパフォーマンスのボトルネックになります。推奨される提案は、ループの外に移動することです。

この修正で上記のコードを拡張すると、

( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
    cmdToRun [option] -- "$file"
done > results.out

これは基本的に、stdoutへのファイル入力の反復ごとにコマンドの内容を配置し、ループが終了したら、stdoutの内容を書き込んで保存するためにターゲットファイルを1回開きます。find同じものの同等のバージョンは

while IFS= read -r -d '' file; do
    cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out

1
ファイルが存在することを確認するための+1。存在しないディレクトリを検索する場合、$ fileには、有効なファイル名ではない正規表現文字列「/ invald_dir / *」が含まれています。
cdalxndr

3

仕事を成し遂げるための1つの迅速で汚い方法は次のとおりです。

find directory/ | xargs  Command 

たとえば、現在のディレクトリのすべてのファイルの行数を見つけるには、次のようにします。

find . | xargs wc -l

8
@Hubertなぜファイル名に改行があるのですか?
musicin3d

2
「なぜ」の問題ではなく、正確さの問題です。ファイル名に印刷可能な文字を含める必要はなく、有効なUTF-8シーケンスである必要もありません。また、改行とはエンコードに大きく依存します。あるエンコード♀は別の改行です。コードページ437を参照
Hubert Kario

2
本当に?これは時間の99.9%を機能し、彼は「迅速かつ汚い」と言いました
エドアルド

私は「速くて汚い」(別名「壊れた」)Bashスクリプトのファンではありません。遅かれ早かれ、それは有名な「移動し~/.local/share/steamた。Ranスチーム。ユーザーが所有するシステム上のすべてを削除した」のようなもので終わる。バグレポート。
活動の削減

これは、名前にスペースが含まれているファイルでも機能しません。
シャマスS-モニカを復活させる

2

私はすべての.mdファイルを1つのディレクトリから別のディレクトリにコピーする必要があったので、ここで私がやったことです。

for i in **/*.md;do mkdir -p ../docs/"$i" && rm -r ../docs/"$i" && cp "$i" "../docs/$i" && echo "$i -> ../docs/$i"; done

これはかなり読みにくいので、分解してみましょう。

最初に、ファイルのあるディレクトリにcdします。

for i in **/*.md; パターン内の各ファイル

mkdir -p ../docs/"$i"ファイルを含むフォルダーの外のdocsフォルダーにそのディレクトリを作成します。これにより、そのファイルと同じ名前で追加のフォルダーが作成されます。

rm -r ../docs/"$i" の結果として作成された余分なフォルダを削除します mkdir -p

cp "$i" "../docs/$i" 実際のファイルをコピーする

echo "$i -> ../docs/$i" あなたがしたことをエコー

; done 末永く幸せに過ごす


注:のために**仕事に、globstarシェルオプションを設定する必要がありますshopt -s globstar
ヒューバートKario

2

使用できます xarg

ls | xargs -L 1 -d '\n' your-desired-command

-L 1 一度に1つのアイテムを渡す

-d '\n'の出力lsは改行に基づいて分割されます。


1

@ジム・ルイスのアプローチに基づく:

ここでは、findファイルを使用し、変更日でファイルを並べ替える簡単なソリューションを示します。

$ find  directory/ -maxdepth 1 -type f -print0 | \
  xargs -r0 stat -c "%y %n" | \
  sort | cut -d' ' -f4- | \
  xargs -d "\n" -I{} cmd -op1 {} 

並べ替えについては、以下を参照してください。

http://www.commandlinefu.com/commands/view/5720/find-files-and-list-them-sorted-by-modification-time


ファイルの名前に改行がある場合、これは機能しません
Hubert Kario

1
@HubertKario -print0for find-0for について、xargsホワイトスペース(改行を含む)の代わりにnull文字を使用することについての詳細を読みたい場合があります。
tuxdna

はい、使用-print0することは役立ちますが、パイプライン全体sortはこのようなものを使用する必要があり、そうではありません
Hubert Kario

1

簡単な解決策は次のとおりです。

sh /dir/* > ./result.txt

2
質問を正しく理解しましたか?これは、単にスクリプトのように、シェルを介してディレクトリ内の各ファイルを実行しようとするだけです。
rdas

1

最大深度

私はそれがジム・ルイスの答えでうまく機能することを発見しました。

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
$ find . -maxdepth 1 -type f -name '*.sh' -exec {} \; > results.out

ソート順

ソート順で実行したい場合は、次のように変更します。

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -maxdepth 2 -type f -name '*.sh' | sort | bash > results.out

例として、これは次の順序で実行されます。

bash: 1: ./assets/main.sh
bash: 2: ./builder/clean.sh
bash: 3: ./builder/concept/compose.sh
bash: 4: ./builder/concept/market.sh
bash: 5: ./builder/concept/services.sh
bash: 6: ./builder/curl.sh
bash: 7: ./builder/identity.sh
bash: 8: ./concept/compose.sh
bash: 9: ./concept/market.sh
bash: 10: ./concept/services.sh
bash: 11: ./product/compose.sh
bash: 12: ./product/market.sh
bash: 13: ./product/services.sh
bash: 14: ./xferlog.sh

無制限の深さ

特定の条件で無制限の深さで実行したい場合は、これを使用できます:

export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -type f -name '*.sh' | sort | bash > results.out

次に、次のように子ディレクトリの各ファイルの上に配置します。

#!/bin/bash
[[ "$(dirname `pwd`)" == $DIR ]] && echo "Executing `realpath $0`.." || return

親ファイルの本文のどこかに:

if <a condition is matched>
then
    #execute child files
    export DIR=`pwd`
fi
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.