現在のディレクトリを繰り返し、繰り返しなしで配列にディレクトリとサブディレクトリの名前を保存します。

現在のディレクトリを繰り返し、繰り返しなしで配列にディレクトリとサブディレクトリの名前を保存します。

特定のパスのすべてのディレクトリとサブディレクトリを繰り返すシナリオがあります。特定の拡張子(.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つまり、隠された名前の一致を有効にし、一致がまったくない場合はパターンを削除します。最後は、生成されたパス名の「ヘッダー」、つまり末尾にファイル名がないディレクトリパスを提供します。Ndotglobnullglobbash:h

シェルオプションを設定する必要があるため、シェルを使用するために明示的に有効にzshする必要はありません。**bashglobstar

その後、印刷する

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"; }

関連情報