回答:
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort |uniq
上記は、現在のディレクトリ(.
)の下にある通常のファイル(-type f
)でありf
、名前のどこかにあるすべてのファイル()を検索します-name '*f*'
。次に、sed
ファイル名を削除し、ディレクトリ名のみを残します。次に、ディレクトリのリストがソートされ(sort
)、重複が削除されます(uniq
)。
sed
コマンドは、1つの代替で構成されています。正規表現への一致を探し、一致する/[^/]+$
ものを何も置き換えません。ドル記号は行の終わりを意味します。 [^/]+'
スラッシュではない1つ以上の文字を意味します。したがって、/[^/]+$
最後のスラッシュから行末までのすべての文字を意味します。つまり、これはフルパスの末尾のファイル名と一致します。したがって、sedコマンドはファイル名を削除し、ファイルがあったディレクトリの名前を変更せずに残します。
最新のsort
コマンドの多く-u
は、uniq
不要なフラグをサポートしています。GNU sedの場合:
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort -u
そして、MacOS sedの場合:
find . -type f -name '*f*' | sed -E 's|/[^/]+$||' |sort -u
また、find
コマンドでサポートされている場合はfind
、ディレクトリ名を直接印刷することもできます。これにより、以下の必要がなくなりますsed
。
find . -type f -name '*f*' -printf '%h\n' | sort -u
上記のバージョンは、改行を含むファイル名と混同されます。より堅牢なソリューションは、NULで終了する文字列でソートを行うことです。
find . -type f -name '*f*' -printf '%h\0' | sort -zu | sed -z 's/$/\n/'
-E
がMacOS向けに更新されました。
これを試してみてください:
find / -name '*f*' -printf "%h\n" | sort -u
find
は実際には非常にまばらであり、-printf
演算子は指定されていません。これはBSDでは機能しませんfind
。したがって、「完全にPOSIX互換」ではありません。(ただしsort -u
POSIXにあります。)
これを行うには、基本的に2つの方法があります。1つは文字列を解析し、もう1つは各ファイルを操作します。以下のようなツールの文字列の使用を解析するgrep
、sed
またはawk
明らかに速いことになるだろうが、ここでは両方を示す一例だ、だけでなく、どのようにすることができます「プロファイル」の2つの方法です。
以下の例では、次のデータを使用します
$ touch dir{1..3}/dir{100..112}/file{1..5}
$ touch dir{1..3}/dir{100..112}/nile{1..5}
$ touch dir{1..3}/dir{100..112}/knife{1..5}
*f*
からいくつかのファイルを削除しますdir1/*
:
$ rm dir1/dir10{0..2}/*f*
ここでは、以下のツールを使用するつもりだfind
、grep
とsort
。
$ find . -type f -name '*f*' | grep -o "\(.*\)/" | sort -u | head -5
./dir1/dir103/
./dir1/dir104/
./dir1/dir105/
./dir1/dir106/
./dir1/dir107/
以前と同じツールチェーン。ただし、今回はのdirname
代わりに使用しますgrep
。
$ find . -type f -name '*f*' -exec dirname {} \; | sort -u | head -5
./dir1/dir103
./dir1/dir104
./dir1/dir105
./dir1/dir106
./dir1/dir107
注:上記の例はhead -5
、これらの例で扱っている出力の量を単に制限するために使用しています。通常は、完全なリストを取得するために削除されます!
我々は使用することができますtime
2つのアプローチを見てみましょうします。
dirname
real 0m0.372s
user 0m0.028s
sys 0m0.106s
grep
real 0m0.012s
user 0m0.009s
sys 0m0.007s
そのため、可能な場合は常に文字列を処理するのが最善です。
grepおよびPCRE
$ find . -type f -name '*f*' | grep -oP '^.*(?=/)' | sort -u
sed
$ find . -type f -name '*f*' | sed 's#/[^/]*$##' | sort -u
awk
$ find . -type f -name '*f*' | awk -F'/[^/]*$' '{print $1}' | sort -u
この答えは、slmの答えに恥知らずに基づいています。興味深いアプローチでしたが、ファイル名やディレクトリ名に特別な文字(スペース、半列など)が含まれている場合には制限があります。良い習慣は使用することfind /somewhere -print0 | xargs -0 someprogam
です。
以下の例では、次のデータを使用します
mkdir -p dir{1..3}/dir\ {100..112}
touch dir{1..3}/dir\ {100..112}/nile{1..5}
touch dir{1..3}/dir\ {100..112}/file{1..5}
touch dir{1..3}/dir\ {100..112}/kni\ fe{1..5}
*f*
からいくつかのファイルを削除しますdir1/*/
:
rm dir1/dir\ 10{0..2}/*f*
$ find -type f -name '*f*' -print0 | sed -e 's#/[^/]*\x00#\x00#g' | sort -zu | xargs -0 -n1 echo | head -n5
./dir1/dir 103
./dir1/dir 104
./dir1/dir 105
./dir1/dir 106
./dir1/dir 107
注:上記の例はhead -5
、これらの例で扱っている出力の量を単に制限するために使用しています。通常は、完全なリストを取得するために削除されます!また、echo
使用するコマンドをwhich に置き換えます。
でzsh
:
typeset -aU dirs # array with unique values
dirs=(**/*f*(D:h))
printf '%s\n' $dirs
uniq
ミックスにしても、すでにお互いに隣している重複行を削除することで多くのことができます。find . -type f -name '*f*' -printf '%h\0' | uniq -z | sort -zu | tr '\0' '\n'
。または、ツールが少し古い場合、uniqに-zオプションがない場合があります。find . -type f -name '*f*' -printf '%h\n' | uniq | sort -u