特定のパスのすべてのディレクトリとサブディレクトリを繰り返すシナリオがあります。特定の拡張子(.txt)を持つファイルが見つかったら、ディレクトリとサブディレクトリの名前を配列に保存します。後でこのディレクトリからコマンドを読み取り、実行します。
私がやっていることは次のとおりです。
!/bin/bash
x=( $(find . -name "*.txt") ); echo "${x[@]}"
for item in "${x[@]}"; { echo "$item"; }
私の現在の出力は次のとおりです
./dir1/file1.txt
./dir1/file2.txt
./dir2/subdir1/subdir2/file3.txt
しかし、私が達成したいのは、x
ディレクトリに複数の.txt
ファイルが含まれていても、配列に重複したファイルがあってはならないということです。また、ファイル名をパスとして保存したくありません。配列にはディレクトリ名のみを含める必要があります。
予想出力:
./dir1
./dir2/subdir1/subdir2/
答え1
使用bash
:
shopt -s globstar
shopt -s dotglob nullglob
dirs=( ./**/*.txt ) # glob the names
dirs=( "${dirs[@]%/*}" ) # remove the filenames at the end
これにより、重複する可能性があるディレクトリパスの配列が提供されます。重複を削除するには、連想配列を使用します。
declare -A seen
for dirpath in "${dirs[@]}"; do
seen["$dirpath"]=''
done
dirs=( "${!seen[@]}" ) # extract the keys from the "seen" hash
その後、印刷する
printf '%s\n' "${dirs[@]}"
シェルでもzsh
同様に実行できますが、一意の配列とシェルの素晴らしいグローバル修飾子を使用してパスの末尾にあるファイル名を削除します。
typeset -U dirs
dirs=( ./**/*.txt(DN:h) )
パターンの後の and in ワイルドカード修飾子は and in として機能します。D
つまり、隠された名前の一致を有効にし、一致がまったくない場合はパターンを削除します。最後は、生成されたパス名の「ヘッダー」、つまり末尾にファイル名がないディレクトリパスを提供します。N
dotglob
nullglob
bash
:h
シェルオプションを設定する必要があるため、シェルを使用するために明示的に有効にzsh
する必要はありません。**
bash
globstar
その後、印刷する
print -r -C1 -- $dirs
また関連:
答え2
変形:
#!/bin/bash
x=$(for f in $(find . -name "*.txt"); do echo "${f%/*}"; done | sort -u )
for item in "${x[@]}"; { echo "$item"; }