UNIX / Linuxシェルでパターンマッチングを行うときに、逆または負のワイルドカードを使用するにはどうすればよいですか?
-
03-07-2019 - |
質問
名前に「Music」という単語が含まれるファイルとフォルダを除くディレクトリの内容をコピーするとします。
cp [exclude-matches] *Music* /target_directory
これを達成するには、[exclude-matches]の代わりに何をすべきですか?
解決
Bashでは、次のように extglob
オプションを有効にすることで実行できます( ls
を cp
に置き換えて、ターゲットディレクトリを追加します。コース)
~/foobar> shopt extglob
extglob off
~/foobar> ls
abar afoo bbar bfoo
~/foobar> ls !(b*)
-bash: !: event not found
~/foobar> shopt -s extglob # Enables extglob
~/foobar> ls !(b*)
abar afoo
~/foobar> ls !(a*)
bbar bfoo
~/foobar> ls !(*foo)
abar bbar
後でextglobを無効にできます
shopt -u extglob
他のヒント
extglob
シェルオプションを使用すると、コマンドラインでより強力なパターンマッチングが可能になります。
shopt -s extglob
で有効にし、 shopt -u extglob
で無効にします。
この例では、最初に次のことを行います。
$ shopt -s extglob
$ cp !(*Music*) /target_directory
利用可能な完全な ext 終了 glob bing演算子は( man bash
からの抜粋):
shoptビルトインを使用してextglobシェルオプションが有効になっている場合、 パターン一致演算子が認識されます。以下の説明では、 tern-listは、|で区切られた1つ以上のパターンのリストです。複合パターン 次のサブパターンの1つ以上を使用して形成できます。
- ?(pattern-list)
指定されたパターンの0回または1回の出現に一致します- *(pattern-list)
指定されたパターンの0回以上の出現に一致します- +(pattern-list)
指定されたパターンの1つ以上の出現に一致します- @(pattern-list)
指定されたパターンのいずれかに一致- !(pattern-list)
指定されたパターンの1つ以外のすべてに一致します
したがって、たとえば、現在のディレクトリにある .c
または .h
ファイル以外のすべてのファイルを一覧表示するには、次のようにします。
$ ls -d !(*@(.c|.h))
もちろん、通常のシェルグロビングは機能するため、最後の例は次のように書くこともできます。
$ ls -d !(*.[ch])
bashではありません(私が知っていることですが):
cp `ls | grep -v Music` /target_directory
これはまさにあなたが探していたものではないことは知っていますが、それはあなたの例を解決します。
execコマンドを使用することによるメモリコストを回避したい場合は、xargsを使用するとより良い結果が得られると思います。次の方がより効率的な代替手段だと思います
find foo -type f ! -name '*Music*' -exec cp {} bar \; # new proc for each exec
find . -maxdepth 1 -name '*Music*' -prune -o -print0 | xargs -0 -i cp {} dest/
bashでは、 shopt -s extglob
の代わりに GLOBIGNORE
変数。それほど良くはありませんが、覚えやすいと思います。
元のポスターが欲しかった例:
GLOBIGNORE="*techno*"; cp *Music* /only_good_music/
完了したら、 GLOBIGNOREを設定解除
して、ソースディレクトリで rm * techno *
できるようにします。
非常に単純な for
ループを使用することもできます:
for f in `find . -not -name "*Music*"`
do
cp $f /target/dir
done
私の個人的な好みは、grepとwhileコマンドを使用することです。これにより、強力でありながら読み取り可能なスクリプトを記述できるため、最終的に目的どおりに実行できます。さらに、エコーコマンドを使用すると、実際の操作を実行する前にドライランを実行できます。例:
ls | grep -v "Music" | while read filename
do
echo $filename
done
は、コピーするファイルを印刷します。リストが正しい場合、次のステップは次のように単にechoコマンドをcopyコマンドに置き換えることです。
ls | grep -v "Music" | while read filename
do
cp "$filename" /target_directory
done
このための1つの解決策は、findで見つけることができます。
$ mkdir foo bar
$ touch foo/a.txt foo/Music.txt
$ find foo -type f ! -name '*Music*' -exec cp {} bar \;
$ ls bar
a.txt
検索には非常に多くのオプションがあり、含めるものと除外するものを具体的に指定できます。
編集:コメントのAdamは、これは再帰的であると述べました。 findoption mindepthとmaxdepthは、これを制御するのに役立ちます。
ここでまだ見たことのない、 extglob
、 find
、または grep
を使用しないトリックは、2つのファイルを扱うことです。セットとしてリストし、" diff" を使用して comm
を使用します:
comm -23 <(ls) <(ls *Music*)
comm
は、余分な残骸がないため、 diff
よりも望ましいです。
これは、セット1のすべての要素 ls
を返しますが、これらはセット2 ls * Music *
にも含まれません。これには、適切に機能するために両方のセットがソートされている必要があります。 ls
とglob展開には問題ありませんが、 find
などを使用している場合は、必ず sort
を呼び出してください。
comm -23 <(find . | sort) <(find . | grep -i '.jpg' | sort)
潜在的に有用。
次の作品は、数字で始まるファイルを除く、現在のディレクトリ内のすべての *。txt
ファイルをリストします。
これは、 bash
、 dash
、 zsh
およびその他すべてのPOSIX互換シェルで機能します。
for FILE in /some/dir/*.txt; do # for each *.txt file
case "${FILE##*/}" in # if file basename...
[0-9]*) continue ;; # starts with digit: skip
esac
## otherwise, do stuff with $FILE here
done
-
1行目では、パターン
/ some / dir / *。txt
により、for
ループが/ some /のすべてのファイルを反復処理します。名前が
。.txt
で終わるdir -
2行目では、不要なファイルを取り除くためにcaseステートメントが使用されています。 &#8211;
$ {FILE ## * /}
式は、ファイル名から先頭のディレクトリ名コンポーネント(ここでは/ some / dir /
)を取り除き、パターンがファイルのベース名。 (サフィックスに基づいてファイル名のみを除外する場合は、代わりに$ FILE
に短縮できます。) -
3行目では、
case
パターン[0-9] *
)行に一致するすべてのファイルがスキップされます(continue
ステートメントは、for
ループの次の反復にジャンプします)。 &#8211;必要に応じて、ここでもっと面白いことをすることができます。[!a-z] *
を使用して文字(a&#8211; z)で始まらないすべてのファイルをスキップしたり、複数のパターンを使用していくつかの種類のファイル名をスキップしたりできます。[0-9] * | * .bak
は、.bak
ファイルと数字で始まらないファイルの両方をスキップします。
これは正確に「音楽」を除外して実行します
cp -a ^'Music' /target
これと、Music?*や*?Musicなどを除外するもの
cp -a ^\*?'complete' /target
cp -a ^'complete'?\* /target