コマンドツリーのようにディレクトリ内のすべてのファイルを繰り返し、すべてのファイル名を印刷する方法

コマンドツリーのようにディレクトリ内のすべてのファイルを繰り返し、すべてのファイル名を印刷する方法

treeコマンドを模倣し、ディレクトリ内のすべてのファイルとサブディレクトリを繰り返し、すべてのファイル名をエコーするにはどうすればよいですか?

私はディレクトリ内のサブディレクトリがまだディレクトリと見なされていると思い、次のようにしました。

for FILE in *; do
if [ -d $FILE ]
then echo "$FILE is a directory";
else echo "$FILE is a file"
fi
done

サブディレクトリ内で再帰とループを作成し、すべてのファイルを繰り返し、ファイル名+パスを印刷するにはどうすればよいですか?

bash を使用するシェルスクリプトです。ありがとうございます!

答え1

以下は、コマンドで実装されたterdonシェル関数と同じですfind

find . -exec sh -c 'for f; do
                      [ -d "$f" ] && is_dir="" || is_dir="not "
                      printf "\"%s\" is %sa directory\n" "$f" "$is_dir"
                    done' sh {} +

またはfind&perlを使用します(各ディレクトリレベルを2つのスペースにインデントし、ファイル名区切り文字としてNULを使用します)。

find . -print0 | \
  perl -0ne '$is_dir = -d $_ ? "" : "not ";
             $f = $_; $indent = ($f =~ s=/==g);
             printf "  " x $indent . "%s is %sa directory\n", $_, $is_dir'

ほとんどのPerlの1行コードは単純で明確ですが、説明が必要な場合があります。

$f = $_; $indent = ($f =~ s=/==g);

これは$_現在の入力レコード(つまりファイル名)を変数にコピーします$f。次に、/変更回数を変数に保存しながら$ fを変更してすべての文字を削除します$indent$fこれは廃止され、変更を防ぐための1回限りの変数です$_

この変数は、printf形式の文字列を構築するために$indent反復演算子(x乗算に似ていますが、文字列については参照)と一緒に使用されます。man perlop


ファイルとディレクトリの数を数えるには、1行に3桁の広い数字を使用します。

$ find . -print0 | perl -0ne '
    if ( -d $_ ) {
      $is_dir = "";
      $dirs++
    } else {
      $is_dir = "not ";
      $files++
    };

    $f = $_; $indent = ($f =~ s=/==g);
    #printf "  " x $indent . "%s is %sa directory\n", $_, $is_dir;
    printf "%3i: " . "  " x $indent . "%s is %sa directory\n", $., $_, $is_dir;

    END {
      # simple method to determine singular or plural word forms
      # without using the Lingua::EN::Inflect module
      # (see https://metacpan.org/release/Lingua-EN-Inflect)
      my $d_word = ($dirs  != 1) ? "directories" : "directory";
      my $f_word = ($files != 1) ? "files" : "file";

      print "\n$dirs $d_word, $files $f_word\n";
    }'

この行のPerl部分は、コマンドラインで編集することがもはや合理的ではない点に達しました。とんでもない長い行を扱うのは完全なPITAだからです。テキストエディタを使用する方がはるかに簡単です。 $PATHのどこかにスクリプトファイル(つまり、~/bin/存在しない場合はPATHに追加)に最初の行として保存する必要があります。/usr/local/bin#!/usr/bin/perl -0nechmod +x

答え2

ディレクトリを他のすべてとは別に維持したい場合は、再帰関数を使用して次のようにできます。

#!/bin/bash

## Do not expand globs to themselves if they don't match
shopt -s nullglob

list_files(){
  ## if a target has been passed as an argument, use that; if not,
  ## default to '.', the current directory.
  target=${1:-.}
  for i in "$target"/*; do
    if [ -d "$i" ]; then
      list_files "$i"
    else
      echo "$i is not a directory"
    fi
  done
}

list_files "$@"

次のディレクトリ構造が与えられた場合:

$ tree
.
├── 1.txt
├── 2.txt
├── 3.txt
├── dir1
│   ├── subdir1
│   │   ├── file
│   │   ├── subsubdir1
│   │   └── subsubdir2
│   └── subdir2
│       ├── file
│       ├── subsubdir1
│       └── subsubdir2
├── dir2
│   ├── subdir1
│   │   ├── subsubdir1
│   │   └── subsubdir2
│   └── subdir2
│       ├── subsubdir1
│       └── subsubdir2
└── symlink -> 2.txt

15 directories, 6 files

上記のコードは以下を生成します。

$ bar.sh
"./1.txt" is not a directory
"./2.txt" is not a directory
"./3.txt" is not a directory
"./dir1" is a directory
"./dir1/subdir1" is a directory
"./dir1/subdir1/file" is not a directory
"./dir1/subdir1/subsubdir1" is a directory
"./dir1/subdir1/subsubdir2" is a directory
"./dir1/subdir2" is a directory
"./dir1/subdir2/file" is not a directory
"./dir1/subdir2/subsubdir1" is a directory
"./dir1/subdir2/subsubdir2" is a directory
"./dir2" is a directory
"./dir2/subdir1" is a directory
"./dir2/subdir1/subsubdir1" is a directory
"./dir2/subdir1/subsubdir2" is a directory
"./dir2/subdir2" is a directory
"./dir2/subdir2/subsubdir1" is a directory
"./dir2/subdir2/subsubdir2" is a directory
"./symlink" is not a directory

また、ディレクトリツリーの深さを表すオフセットを追加し、すべてのディレクトリを最初に処理してより整列した出力を取得すると、読みやすくなります。

#!/bin/bash

## Do not expand globs to themselves if they don't match
shopt -s nullglob

list_files(){
  ## if a target has been passed as an argument, use that; if not,
  ## default to '.', the current directory.
  target=${1:-.}

  for i in "$target"/*; do
    offset=${2:-""}
    if [ -d "$i" ]; then
      printf '%s"%s" is a directory\n' "$offset" "$i"
      for subdir in "$i"/*/; do
        new_offset="$offset   "
        list_files "$i" "$new_offset"
        new_offset=""
      done
    else
      printf '%s"%s" is not a directory\n' "$offset" "$i" 
    fi
  done
}

list_files "$@"

これにより、上記の入力例に対して次の出力が生成されます。

$ foo.sh 
"./1.txt" is not a directory
"./2.txt" is not a directory
"./3.txt" is not a directory
"./dir1" is a directory
   "./dir1/subdir1" is a directory
      "./dir1/subdir1/file" is not a directory
      "./dir1/subdir1/subsubdir1" is a directory
      "./dir1/subdir1/subsubdir2" is a directory
         "./dir1/subdir1/subsubdir2/file" is not a directory
   "./dir1/subdir2" is a directory
      "./dir1/subdir2/file" is not a directory
      "./dir1/subdir2/subsubdir1" is a directory
      "./dir1/subdir2/subsubdir2" is a directory
"./dir2" is a directory
   "./dir2/subdir1" is a directory
      "./dir2/subdir1/subsubdir1" is a directory
      "./dir2/subdir1/subsubdir2" is a directory
   "./dir2/subdir2" is a directory
      "./dir2/subdir2/subsubdir1" is a directory
      "./dir2/subdir2/subsubdir2" is a directory
"./symlink" is not a directory

このメソッドはシンボリックリンクディレクトリに移動されます。たとえば、次を追加すると:

$ ln -s dir2 dirsym
$ ls -ld dirsym
lrwxrwxrwx 1 terdon terdon 4 Apr  8 16:03 dirsym -> dir2

その後、dirsymディレクトリとして処理され、その内容がターゲットと同じように報告され、出力がdir2効果的に繰り返されます。dir2これが望ましい行動であるかどうかはわかりません。そうでない場合Katzのfindアプローチは反対です。

答え3

(
  find . -type f -print0 | xargs -0 printf '%s is a file\0'
  find . -type d -print0 | xargs -0 printf '%s is a directory\0'
) | sort -z | tr '\0' '\n'

「私ができるか…」のような良い答えなのかはよくわかりません。

ファイルとディレクトリのリストを生成します。

適切なサフィックスを使用して印刷します。

次に、項目が正しいコンテキストに表示されるように、結果全体がソートされます。

名前に改行を含む項目を処理するには、少し余分な作業が必要です。

関連情報