web-dev-qa-db-ja.com

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

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

cp [exclude-matches] *Music* /target_directory

これを達成するために[exclude-matches]の代わりに何をすべきですか?

306
user4812

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
348
Vinko Vrsalovic

extglob Shellオプションを使用すると、コマンドラインでより強力なパターンマッチングを実行できます。

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

この例では、最初に次のことを行います。

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

利用可能な完全なext ended glob bing演算子は(man bashからの抜粋):

Shoptビルトインを使用してextglob Shellオプションが有効になっている場合、いくつかの拡張パターンマッチング演算子が認識されます。以下の説明では、パターンリストは、|で区切られた1つ以上のパターンのリストです。複合パターンは、次のサブパターンの1つ以上を使用して形成できます。

  • ?(パターンリスト)
    指定されたパターンの0回または1回の出現に一致します
  • *(パターンリスト)
    指定されたパターンの0回以上の出現と一致します
  • +(パターンリスト)
    指定されたパターンの1つ以上の出現と一致します
  • @(パターンリスト)
    指定されたパターンのいずれかと一致します
  • !(パターンリスト)
    指定されたパターンの1つを除くすべてに一致

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

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

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

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

Bashではない(知っている)が、:

cp `ls | grep -v Music` /target_directory

これはまさにあなたが探していたものではないことを知っていますが、それはあなたの例を解決します。

22
ejgottl

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

Bashでは、shopt -s extglobの代わりに GLOBIGNORE変数 があります。それほど良くはありませんが、覚えやすいと思います。

元のポスターが欲しかった例:

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

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

5
mivk

私の個人的な好みは、grepとwhileコマンドを使用することです。これにより、強力でありながら読み取り可能なスクリプトを記述できるため、最終的に目的どおりに実行できます。さらに、エコーコマンドを使用すると、実際の操作を実行する前にドライランを実行できます。例えば:

ls | grep -v "Music" | while read filename
do
echo $filename
done

コピーするファイルを印刷します。リストが正しい場合、次のステップは次のように単純にエコーコマンドをコピーコマンドに置き換えることです。

ls | grep -v "Music" | while read filename
do
cp "$filename" /target_directory
done
4
Abid H. Mujtaba

また、非常に単純なforループを使用することもできます。

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

これに対する解決策の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には非常に多くのオプションがあり、含めるものと除外するものをかなり具体的に取得できます。

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

3
Daniel Bungert

extglobfind、またはgrepを使用しない、ここでまだ見たことのないトリックは、2つのファイルリストをセットとして扱い、commを使用して"diff"それらを扱うことです。

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

commは、余分な問題がないため、diffよりも望ましいです。

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

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

潜在的に有用。

3
James M. Lay

次の作品は、数字で始まるものを除き、現在のディレクトリ内のすべての*.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/)から先頭のディレクトリ名コンポーネントを取り除き、パターンがファイルのベース名のみと一致するようにします。 (サフィックスに基づいてファイル名のみを除外する場合は、これを代わりに$FILEに短縮できます。)

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

2
zrajm

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

cp -a ^'Music' /target

これと音楽のようなものを除外するためのあれ?*または*?音楽

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