Linuxの複数のディレクトリでコマンドを実行する

1. 前書き

このチュートリアルでは、複数のディレクトリで同じコマンドを実行するためのいくつかのBashアプローチを検討します。

2. よく見る

まず、*目標をより小さなステップに分解しましょう*:
  1. 現在のフォルダーの内容をリストする

  2. ハードリンクサブフォルダーではないものをすべて除外する

  3. 各サブフォルダーに対してコマンドを実行します

  4. 各サブフォルダーについて、手順1に戻ります

    ロジックは簡単に見えますが、特に実行するコマンドが元に戻せない場合は、ステップ2が実際に非常に重要です。
    実際、*ステップ2がなければ、タッチするつもりのないファイル、またはシンボリックリンクのために予期しない場所で潜在的に危険なコマンドを実行することになります*。
    選択したアプローチに応じて、これらの問題に対処する方法を示します。

3. テスト環境を準備する

そして、実際の問題解決を掘り下げる前に、*環境を準備しましょう*:
# create some folders
for folder in 1 2 3
do
    mkdir folder_$folder
done

# create a sub-directory
mkdir folder_1/sub_folder

# create an empty file
touch my_file

# create a symbolic link pointint to folder_3
ln -s folder_3 my_symbolik_link_to_3
作成したものを確認しましょう:
tree

.
├── folder_1
│   └── sub_folder
├── folder_2
├── folder_3
├── my_file
└── my_symbolik_link_to_3 -> folder_3

5 directories, 1 file
スクリプトの準備がすべて整いました。

4. ループ

Bashでは、さまざまなビルトインを使用してhttps://www.gnu.org/software/bash/manual/bash.html#Looping-Constructs[loop]をプログラムできます。 このセクションでは、それらを1つずつ調べていきます。
いずれかの時点でマニュアルページを見る必要がある場合は、*組み込みの手順がメインのhttps://linux.die.net/man/1/bash[bash man page] *にあることを覚えておく必要があります。これらにアクセスするには、ターミナルで_man bash_を実行し、ページ内で組み込みキーワード(_for _、_ while_、または_until_)を検索する必要があります。
セクション2で説明したロジックを実装するために、2つのテスト条件を使用します。
  • 考慮されるパスがディレクトリの場合、true_を返す-d_

  • -h。考慮されるパスがシンボリックリンクの場合、_true_を返します。

  • _-d_はフォルダーを指すシンボリックリンクを除外しないため、両方の条件が必要です。*

4.1. _for_ループ

forループは非常に便利です。*その範囲構文を使用して、現在のフォルダーの内容を簡単に取得*し、各アイテムをループ処理できるからです。
function recursive_for_loop {
    for f in *;  do
        if [ -d $f  -a ! -h $f ];
        then
            cd -- "$f";
            echo "Doing something in folder `pwd`/$f";

            # use recursion to navigate the entire tree
            recursive_for_loop;
            cd ..;
        fi;
    done;
};
recursive_for_loop
上記のコードは、前述の両方のフィルターを適用します。 *その結果、ファイルやシンボリックリンクはコードによって処理されません*:
# Result
Doing something in folder /home/user/workspace/folder_1
Doing something in folder /home/user/workspace/folder_1/sub_folder
Doing something in folder /home/user/workspace/folder_2
Doing something in folder /home/user/workspace/folder_3
前に定義した条件が、パスに存在するファイルとシンボリックリンクの両方を正常にフィルターで除外したことがわかります。

4.2. _while_ループ

_while_の場合、*範囲から直接読み取ることができないため、代わりに別のコマンドの出力をパイプする必要があります*:
function recursive_for_loop {
    ls -1| while read f; do
        if [ -d $f  -a ! -h $f ];
        then
            cd -- "$f";
            echo "Doing something in folder `pwd`/$f";

            # use recursion to navigate the entire tree
            recursive_for_loop;
            cd ..;
        fi;
    done;
};
recursive_for_loop

# Result
Doing something in folder /home/user/workspace/folder_1
Doing something in folder /home/user/workspace/folder_1/sub_folder
Doing something in folder /home/user/workspace/folder_2
Doing something in folder /home/user/workspace/folder_3

4.3. _until_ループ

untilコンストラクトは、同じ手法を使用してフォルダーのリストを読み取りますが、ループ条件で否定が必要です。
これは、ロジックが異なるためです。条件が_true_の場合、_while_はループ命令を実行し、条件が_false:_の場合、_until_はループ命令を実行します。
function recursive_for_loop {
    ls -1| until ! read f; do
        if [ -d $f  -a ! -h $f ];
        then
            cd -- "$f";
            echo "Doing something in folder `pwd`/$f";

            # use recursion to navigate the entire tree
            recursive_for_loop;
            cd ..;
        fi;
    done;
};
recursive_for_loop

# Result
Doing something in folder /home/user/workspace/folder_1
Doing something in folder /home/user/workspace/folder_1/sub_folder
Doing something in folder /home/user/workspace/folder_2
Doing something in folder /home/user/workspace/folder_3

5. _find_コマンド

*ループの代わりにhttps://linux.die.net/man/1/find[_find_]コマンドを使用します。このコマンドの主な目的は、ディレクトリ階層内のファイルを検索することです。*
記事link:/linux/recently-changed-files[Linuxで最近変更されたファイルを検索]で、_find_を使用して最近変更されたファイルを検索する方法を見てきました。
この例では、2つのオプション、_- exec_と_-execdir_を検討します。どちらも、一致する各ファイルで指定されたコマンドを実行するという同じ目的です。
_-execdir_オプションは同じ結果を達成しますが、一致したファイル(またはこの場合はサブディレクトリ)が存在するディレクトリ内からコマンドを実行し、競合状態を回避するため、_- execdir_オプションの方が安全と見なされます。
*それでも、危険があります:as _execdir_はフォルダーに入った後にコマンドを実行します。これにコマンドと同じ名前の実行可能ファイルが含まれている場合、_find_は意図したものの代わりにローカルコマンドを実行します*
しかし、単純なケースシナリオの場合、違いはありません。
この最後のポイントを証明するために、_- exec_オプションを指定して_find_を実行しましょう。
find ./* -type d -exec touch {}/test \;

# Result
 tree
.
├── folder_1
│   ├── sub_folder
│   │   └── test
│   └── test
├── folder_2
│   └── test
├── folder_3
│   └── test
├── my_file
└── my_symbolik_link_to_3 -> folder_3
コマンドは、各サブディレクトリに「テスト」ファイルを正常に生成しました。
次の手順に進む前に、作成したテストファイルを削除しましょう。
記事link:/linux/delete-files-older-than[Linuxコマンド-Xより古いファイルを削除]のセクション2.4で既に紹介した同じスクリプトを使用できます。
find . -type f -name test -exec rm -i {} \;

# Result
tree
.
├── folder_1
│   └── sub_folder
├── folder_2
├── folder_3
├── my_file
└── my_symbolik_link_to_3 -> folder_3
そして、_- execdir_オプションを指定して_find_を試してみましょう。
find ./* -type d -execdir touch {}/test \;

# Result
tree
.
├── folder_1
│   ├── sub_folder
│   │   └── test
│   └── test
├── folder_2
│   └── test
├── folder_3
│   └── test
├── my_file
└── my_symbolik_link_to_3 -> folder_3
そのため、ケースシナリオでは両方のオプションの結果が同じであることを証明しました。
  • find_とループを比較すると、フィルター条件を使用する必要がないことがわかります。オプション-type d_は、ディレクトリではないものをフィルターで除外し、デフォルトでは、コマンドはシンボリックリンクをたどりません。

    複数のコマンドを実行する場合、同じオプションを複数回繰り返すだけです。
find ./* -type d -execdir echo Doing something in folder {} \; -execdir echo Done something in {} \;

# Result
Doing something in folder ./folder_1
Done something in ./folder_1
Doing something in folder ./sub_folder
Done something in ./sub_folder
Doing something in folder ./folder_2
Done something in ./folder_2
Doing something in folder ./folder_3
Done something in ./folder_3

6. _xargs_コマンド

  • xargsコマンドは、標準入力を使用してコマンドラインをビルドおよび実行します。*

    その後、_find_の出力をパイプ処理し、見つかった各ディレクトリで必要なコマンドを実行できます。
find ./* -type d | xargs -I {} echo Doing something in folder {}

# Result
Doing something in folder ./folder_1
Doing something in folder ./folder_1/sub_folder
Doing something in folder ./folder_2
Doing something in folder ./folder_3

7. 検索の深さを制御する

上記のすべてのケースでは、ディレクトリツリー全体をトラバースする必要があるという前提がありますが、*開始または到達したい深さを制限する場合はどうなりますか?
この目的のために、_find_は_-mindepth_と_-maxdepth_という2つの便利なオプションも備えています。
それらを適用するには、関心のある深さレベルを設定するだけです。ここで、数字のゼロは現在のディレクトリを表します。
以前と同じ動作を再現してみましょう。
find ./* -mindepth 0 -maxdepth 1 -type d -exec echo Doing something in folder {}\;

# Result
Doing something in folder ./folder_1
Doing something in folder ./folder_1/sub_folder
Doing something in folder ./folder_2
Doing something in folder ./folder_3
次に、_maxdepth_を変更して、第1レベルのサブフォルダーのみを検索します。
find ./* -mindepth 0 -maxdepth 0 -type d -exec echo Doing something in folder {}\;

# Result
Doing something in folder ./folder_1
Doing something in folder ./folder_2
Doing something in folder ./folder_3
コマンドが_sub_folder_で実行されていないことがわかります。
そして、代わりに_mindepth_を変更して、ツリーの第2レベルでのみコマンドを実行してみましょう。
find ./* -mindepth 1 -maxdepth 1 -type d -exec echo Doing something in folder {}\;

# Result
Doing something in folder ./folder_1/sub_folder
これまで見てきたように、*これらのオプションは_find_検索をさらに制御しますが、欠点として、処理に関心のあるツリー構造を正確に知る必要があります。*

8. 結論

このチュートリアルでは、ローカルパスのツリーにあるすべてのサブディレクトリでBashコマンドを実行し、Bashビルトインと基本的なLinuxツールをテストするさまざまなアプローチを検討しました。