ディレクトリを変更してコマンドを自動化し、ディレクトリを再変更します。

ディレクトリを変更してコマンドを自動化し、ディレクトリを再変更します。

単一レベルのサブディレクトリが多数ある特定のディレクトリで実行されるスクリプトを作成しようとしています。スクリプトはcd各サブディレクトリに移動し、ディレクトリ内のファイルに対してコマンドを実行して終了し、cd次のディレクトリに進みます。

出力は、元のディレクトリと同じ構造と名前を持つ新しいディレクトリに返される必要があります。これを行う最良の方法は何ですか?

答え1

最良の方法はサブシェルを使用することです。

for dir in */; do
  (cd -- "$dir" && some command there)
done

次の状況を避けてください。

for dir in */; do
  cd -- "$dir" || continue
  some command there
  cd ..
done

または:

here=$PWD
for dir in */; do
  cd -- "$dir" || continue
  some command here
  cd "$here"
done

これは、シンボリックリンクが含まれている場合、またはスクリプトを実行すると現在のディレクトリのパスが変更される可能性がある場合、信頼性が低下するためです。

サブシェルを含まない「正しい」方法は、close-on-execフラグを使用してファイル記述子で現在のディレクトリを開き、サブディレクトリにchdir()移動しますfchdir(fd)。シェルはこれをサポートします。

しかし、あなたはこれを行うことができますperl

#! /usr/bin/perl

opendir DIR, "." or die "opendir: $!";
while (readdir DIR) {
  if (chdir($_)) {
    do-something-there;
    chdir DIR || die "fchdir: $!";
  }
}
closedir DIR

答え2

forループの使用

ディレクトリのサブディレクトリを繰り返すには、forループを使用できます。

for dir in */; do
  echo "Doing something in $dir"
done

ワイルドカードは、*/現在のディレクトリ内のサブディレクトリだけでなく、ディレクトリへのシンボリックリンクも拡張されます。名前が始まるディレクトリ.(つまり、ドットファイルのディレクトリ)は省略されます。シンボリックリンクをスキップするには、明示的にこれを実行できます。

for dir in */; do
  if [ -L "${dir%/}" ]; then continue; fi
  echo "Doing something in $dir"
done

ディレクトリ内のすべてのファイルに対してコマンドを実行するには、*ワイルドカード文字を使用します。繰り返しますが、これにはドットファイルは含まれません。ディレクトリが空の場合、ワイルドカード文字は拡張されません。

for dir in */; do
  if [ -L "${dir%/}" ]; then continue; fi
  if (set -- "$dir"*; [ "$#" -ne 0 ]); then
    somecommand -- "$dir"*
  fi
done

各ファイルに対して個別にコマンドを呼び出す必要がある場合は、入れ子になったループを使用してください。以下のコードスニペットは、ファイルが[ -e "$file" ] || [ -L "$file" ]既存のファイル(壊れたシンボリックリンクを除く)かシンボリックリンク(壊れているかどうか)をテストするために使用されます。"$file"これは、空のディレクトリ条件のためにワイルドカードパターンが拡張されていない場合にのみ機能します。

for dir in */; do
  if [ -L "${dir%/}" ]; then continue; fi
  for file in "$dir"*; do
    if [ -e "$file" ] || [ -L "$file" ]; then
      somecommand -- "$file"
    fi
  done
done

コマンドに現在のディレクトリとしてサブディレクトリが必要な場合は、これを行うには2つの方法があります。cd前後に電話できます。

for dir in */; do
  if [ -L "${dir%/}" ]; then continue; fi
  if cd -- "$dir"; then
    for file in *; do
      if [ -e "$file" ] || [ -L "$file" ]; then
        somecommand -- "$file"
      fi
    done
    cd ..
  fi
done

欠点は、コマンドの実行中に子ディレクトリが移動されたり、プロセスが親ディレクトリに変更する権限がない場合など、極端な場合に失敗する可能性があることです。このような極端なケースを防ぐための別のアプローチcd ..は、サブシェルでコマンドを実行することです。サブシェルは現在のディレクトリを含む元のシェルの状態をコピーし、サブシェルで何が起こっても元のシェルプロセスには影響しません。

for dir in */; do
  if [ -L "${dir%/}" ]; then continue; fi
  ( cd -- "$dir" &&
    for file in *; do
      if [ -e "$file" ] || [ -L "$file" ]; then
        somecommand -- "$file"
      fi
    done
  )
  fi
done

そして検索

ご覧のとおり、名目でない場合を処理することはそれほど簡単ではありません。これらのいくつかは心配する必要がない極端なケースですが、空のディレクトリを処理できるはずです。 ksh、bash、またはzshを使用している場合は、これらの状況を処理するより簡単な方法があります。上記では、通常のshで動作する(そしてドットファイルを処理しない)コードを示しました。

ファイルとディレクトリを列挙する別の方法はfindコマンドです。ディレクトリツリーを再帰的に巡回するように設計されていますが、サブディレクトリがなくても使用できます。 GNUまたはFreeBSDの検索(つまり、組み込みのLinux、Cygwin、FreeBSD、またはOSX)を使用すると、ディレクトリツリー内のすべてのファイルに対してコマンドを実行する簡単な方法があります。

find . ! -type d -execdir somecommand {} \;

ディレクトリ以外のすべてのファイルに対してコマンドが実行されます。通常のファイルでのみ実行します(シンボリックリンクや他の特殊タイプのファイルを除く)。

find . -type f -execdir somecommand {} \;

最上位ディレクトリに直接配置されたファイルを除外するには、次の手順を実行します。

find . -mindepth 2 -type f -execdir somecommand {} \;

サブサブディレクトリが存在し、そのディレクトリに再帰的に入りたくない場合:

find . -mindepth 2 -maxdepth 3 -type f -execdir somecommand {} \;

関連情報