unix / linuxシェルでパターンマッチングを行うときに、逆または負のワイルドカードをどのように使用できますか?


325

名前に「Music」という単語が含まれているファイルやフォルダを除いて、ディレクトリの内容をコピーしたいとします。

cp [exclude-matches] *Music* /target_directory

これを行うには、[除外一致]の代わりに何をすべきですか?

回答:


375

Bash extglobでは、次のようにオプションを有効にすることで実行できます(もちろん、ターゲットディレクトリに置き換えlscp追加します)

~/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

14
私はこの機能が好きです:ls /dir/*/!(base*)
エリック・ロバートソン

6
すべてを含める方法()と除外する方法!(b
Elijah Lynn

4
fを除いて、で始まるすべてのものにどのように一致しfooますか?
Noldorin 2013年

8
これがデフォルトで無効になっているのはなぜですか?
weberc2 14

3
shopt -o -u histexpand感嘆符が含まれているファイルを検索する必要がある場合-デフォルトではextglobはデフォルトでオフになっているので、histexpandに干渉しません。fooを除く、fで始まるすべてに一致します:f!(oo)、もちろん 'food'は一致します( 'foo'で始まるものを停止するにはf!(oo *)が必要です、または、削除したい場合'.foo'で終わる特定のものの!(. foo)を使用するか、プレフィックスを付けます:myprefix!(. foo)(myprefixBLAHに一致しますが、myprefixBLAH.fooには一致しません)
osirisgothra

227

extglobシェルのオプションを使用すると、コマンドラインでより強力なパターンマッチングを提供します。

でオンにしshopt -s extglob、でオフにしshopt -u extglobます。

あなたの例では、最初は次のようにします:

$ shopt -s extglob
$ cp !(*Music*) /target_directory

フル利用可能extは終了グロブビング事業者は(からの抜粋ですman bash):

組み込みのshoptを使用してextglobシェルオプションを有効にすると、いくつかの拡張パターンマッチング演算子が認識されます。パターンリストは、|で区切られた1つ以上のパターンのリストです。 複合パターンは、次のサブパターンの1つ以上を使用して形成できます。

  • ?(pattern-list)
    指定されたパターンの0回または1回の出現に一致
  • *(pattern-list)
    指定されたパターンの0回以上の出現に一致
  • +(パターンリスト)
    指定されたパターンの1回以上の出現に一致します
  • @(pattern-list)
    指定されたパターンの1つに一致します
  • !(pattern-list)
    指定されたパターンの1つ以外のすべてに一致します

したがって、たとえば、現在のディレクトリにある、.cまたは.hファイルではないすべてのファイルを一覧表示する場合は、次のようにします。

$ ls -d !(*@(.c|.h))

もちろん、通常のシェルグロビングは機能するので、最後の例は次のように書くこともできます。

$ ls -d !(*.[ch])

1
-dの理由は何ですか?
Big McLargeHuge 2015

2
.cまたは.hファイルの1つがディレクトリである場合の@Koveras 。
tzot 2015

@DaveKennedy現在のディレクトリ内のすべてを一覧表示しますが、Dディレクトリに含まれている可能性のあるサブディレクトリの内容は表示しませんD
spurra

23

(私が知っている)bashではありませんが、:

cp `ls | grep -v Music` /target_directory

これはあなたが探していたものとは正確には違いますが、あなたの例を解決します。


デフォルトのlsは1行に複数のファイルを配置しますが、おそらく正しい結果が得られません。
Daniel Bungert、

10
stdoutが端末の場合のみ。パイプラインで使用すると、lsは1行に1つのファイル名を出力します。
Adam Rosenfield、

lsは、端末に出力する場合にのみ、1行に複数のファイルを配置します。自分で試してください-「ls | less」では、1行に複数のファイルが含まれることはありません。
SpoonMeiser 2008年

3
スペース(または他の白いスペース文字)を含むファイル名に対しては機能しません。
tzot

7

execコマンドを使用することによるmemコストを回避したい場合は、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/

6

bashでは、への代替shopt -s extglobGLOBIGNORE変数です。良くはありませんが、覚えやすいです。

元のポスターが望んでいたものの例:

GLOBIGNORE="*techno*"; cp *Music* /only_good_music/

完了したら、ソースディレクトリでunset GLOBIGNORE実行できるようにrm *techno*します。


5

かなり単純なforループを使用することもできます。

for f in `find . -not -name "*Music*"`
do
    cp $f /target/dir
done

1
これは再帰的な検索を行います。これは、OPが必要とする動作とは異なる動作です。
Adam Rosenfield、

1
-maxdepth 1非再帰的に使用しますか?
avtomaton 2016年

シェルオプションを有効/無効にする必要がない、これが最もクリーンなソリューションであることがわかりました。この投稿では、OPが必要とする結果を得るために-maxdepthオプションをお勧めしますが、それはすべて、達成しようとしていることに依存します。
David Lapointe

find重要でないファイル名が見つかった場合、バッククォートで使用すると不快な方法で壊れます。
Tripleee、2018年

5

私の個人的な好みは、grepとwhileコマンドを使用することです。これにより、強力で読みやすいスクリプトを記述して、最終的に目的どおりの操作を実行できるようになります。さらに、echoコマンドを使用して、実際の操作を実行する前に予行演習を実行できます。例えば:

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
これは、ファイル名にタブ、改行、連続した複数のスペース、またはバックスラッシュがない限り機能します。これらは病的なケースですが、可能性を知っておくことは良いことです。でbash使用できますwhile IFS='' read -r filenameが、改行はまだ問題です。一般に、lsファイルの列挙には使用しないことをお勧めします。のようなツールfindがはるかに適しています。
Thedward

追加のツールなし:for file in *; do case ${file} in (*Music*) ;; (*) cp "${file}" /target_directory ; echo ;; esac; done
Thedward

mywiki.wooledge.org/ParsingLsには、これを回避する必要があるその他の理由がいくつか記載されています。
tripleee

5

私が使用していないこと、まだここで見たことがないトリックextglobfindまたはgrepセットとして2つのファイルのリストを処理することで、「差分」彼らが使用してcomm

comm -23 <(ls) <(ls *Music*)

commdiff余分なくずがないので、より望ましいです。

これは、セット1のすべての要素を返しますlsが、セット2にも含まれていませんls *Music*。これは、適切に機能するために両方のセットがソートされた順序であることが必要です。lsとglob展開に問題はありませんが、のようなものを使用している場合はfind、必ずを呼び出してくださいsort

comm -23 <(find . | sort) <(find . | grep -i '.jpg' | sort)

潜在的に有用です。


1
除外の利点の1つは、最初にディレクトリを走査しないことです。このソリューションは、サブディレクトリの2つのトラバーサルを実行します。1つは除外あり、もう1つは除外です。
Mark Stosberg、2017

@MarkStosberg、非常に良い点です。この技術の1つのフリンジ利点は、例えば実際のファイルからの除外を読むことができるである、けれどもcomm -23 <(ls) exclude_these.list
ジェームスM.レイ

3

このための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

Findにはかなりの数のオプションがあり、含めるものと除外するものをかなり具体的にすることができます。

編集:コメントのアダムは、これは再帰的であると述べました。オプションfindmindthおよびmaxdepthは、これを制御するのに役立ちます。


これは再帰的なコピーを行いますが、これは異なる動作です。また、ファイルごとに新しいプロセスを生成します。これは、多数のファイルに対して非常に非効率になる可能性があります。
Adam Rosenfield、

プロセスを生成するコストは、各ファイルのコピーによって生成されるすべてのIOと比較すると、ほぼゼロです。ですから、これは時々の使用には十分です。
ランド:2008年

プロセス・産卵のためのいくつかの回避策:stackoverflow.com/questions/186099/...
Vinko Vrsalovic

再帰を回避するには、 "-maxdepth 1"を使用してください。
ejgottl 2008年

バックティックを使用して、シェルのワイルドカード拡張の類似物を取得しますfind -maxdepth 1 -not -name '*Music*'
。cp

2

次の作品は*.txt、数字で始まるものを除いて、現在のディレクトリ内のすべてのファイルをリストします。

これはで動作しbashdashzshおよび他のすべての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. 1行目では、パターン/some/dir/*.txtにより、名前がで終わるforすべてのファイルに対してループが繰り返されます。/some/dir.txt

  2. 2行目では、case文を使用して不要なファイルを除外しています。– ${FILE##*/}式は、ファイル名(ここでは/some/dir/)から先頭のdir名コンポーネントを削除して、パターンがファイルのベース名のみと照合できるようにします。(サフィックスに基づいてファイル名のみを削除する場合は、$FILE代わりにこれを短縮できます。)

  3. 3 行目では、caseパターン[0-9]*)行に一致するすべてのファイルがスキップされます(continueステートメントはforループの次の反復にジャンプします)。–ここでもっと面白いことをしたい場合は、たとえば、を使用して文字(a〜z)で始まらないすべてのファイル[!a-z]*をスキップするか、複数のパターンを使用して複数の種類のファイル名[0-9]*|*.bakをスキップするなど、.bakファイルを両方ともスキップすることができます。 、および番号で始まらないファイル。


どー!バグがありました(私はの*.txt代わりに照合しました*)。今修正されました。
zrajm

0

これは正確に「音楽」を除いてそれを行います

cp -a ^'Music' /target

これと、ミュージックのようなものを除外するためのものですか?*または*?

cp -a ^\*?'complete' /target
cp -a ^'complete'?\* /target

cpMacOS のマニュアルページには-aオプションがありますが、まったく異なることを行います。どのプラットフォームがこれをサポートしていますか?
Tripleee、2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.