web-dev-qa-db-ja.com

ディレクトリ内のすべてのファイルに対してコマンドを実行するためのBashスクリプト

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

たとえば、1ファイルに対してコマンドを実行するには、次のようにします。

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

次のbashコードは$ fileをcommandに渡します。$ 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
343
Andrew Logvinov

これはどう:

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がinode順にファイルを処理していると思いますが(それは間違っているかもしれませんが)、それはあなたが望むものではないかもしれません。

144
Jim Lewis

Raspberry Piのコマンドラインから次のコマンドを実行しています。

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

あるディレクトリから別のディレクトリにすべての.mdファイルをコピーする必要があったので、ここでこれを行いました

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その後もずっと幸せに暮らしています

2
Eric Wooley

仕事を終わらせるための1つの迅速で汚い方法は:

find directory/ | xargs  Command 

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

find . | xargs wc -l
2
Rahul

@Jim Lewisのアプローチに基づきます。

これは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

1
tuxdna

私は簡単な解決策だと思います:

sh /dir/* > ./result.txt
1
yovie

承認された/賛成された回答は素晴らしいですが、彼らはいくつかの重要な詳細を欠いています。この記事では、シェルのパス名展開(glob)が失敗したとき、ファイル名に改行/ダッシュ記号が埋め込まれているとき、およびコマンド出力方向をforループの外側に移動するときの対処方法について説明します。

*を使用してShell glob展開を実行するとき、ディレクトリに no ファイルが存在せず、展開されていないglob文字列がコマンドに渡されると、展開が失敗する可能性があります。望ましくない結果をもたらす可能性があるファイル上で実行してください。 bashシェルはnullglobを使用してこれのための拡張シェルオプションを提供します。ですから、ループは基本的にあなたのファイルを含むディレクトリの中で次のようになります。

 shopt -s nullglob

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

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

あるいはPOSIX準拠の方法で(nullglobbash特有です)

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

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

ファイル名引数を渡す直前に--を使用することにも注意してください。前述のように、シェルのファイル名にはファイル名のどこかにダッシュが含まれているため、これが必要です。シェルコマンドの中にはそれを解釈してそれらをコマンドオプションとして扱い、フラグが与えられていればコマンドを実行します。

その場合、--はコマンドラインオプションの終わりを示します。つまり、この時点以降の文字列はコマンドフラグとして解析されるのではなく、ファイル名としてのみ解析されます。


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

そのため、Shellのprintfオプションを使用して、readコマンドの-dオプションを使用してこのNULLバイトでファイルを区切ると、以下のようになります。

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

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

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

-print0をサポートしないfindの実装(GNUとFreeBSDの実装を除く)では、これはprintfを使ってエミュレートすることができます。

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

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

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

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

これは基本的に、ファイルの入力が繰り返されるたびにコマンドの内容をstdoutに出力し、ループが終了したら、ターゲットファイルを1回開いてstdoutの内容を書き込んで保存します。同じfindバージョンは、

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

マックスデプス

Jim Lewisの答えでうまくいくことがわかりました。

$ 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
0
Chetabahana