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 -0ne
chmod +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'
「私ができるか…」のような良い答えなのかはよくわかりません。
ファイルとディレクトリのリストを生成します。
適切なサフィックスを使用して印刷します。
次に、項目が正しいコンテキストに表示されるように、結果全体がソートされます。
名前に改行を含む項目を処理するには、少し余分な作業が必要です。