Jak używać odwrotnych lub ujemnych symboli wieloznacznych podczas dopasowywania wzorców w powłoce unix / linux?

325

Powiedzmy, że chcę skopiować zawartość katalogu z wyjątkiem plików i folderów, których nazwy zawierają słowo „Muzyka”.

cp [exclude-matches] *Music* /target_directory

Co powinno zostać zastąpione przez [wykluczające mecze], aby to osiągnąć?

użytkowników4812
źródło

Odpowiedzi:

375

W Bash można to zrobić poprzez włączenie extglobopcji, jak to (wymienić lsz cpi dodać katalog docelowy, oczywiście)

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

Możesz później wyłączyć extglob za pomocą

shopt -u extglob
Vinko Vrsalovic
źródło
14
Podoba mi się ta funkcja:ls /dir/*/!(base*)
Erick Robertson,
6
Jak uwzględnić wszystko ( ), a także wykluczyć! (B )?
Elijah Lynn
4
Jak dopasowałbyś, powiedzmy, wszystko, zaczynając od f, oprócz foo?
Noldorin
8
Dlaczego jest to domyślnie wyłączone?
weberc2
3
shopt -o -u histexpand, jeśli chcesz szukać plików z wykrzyknikami - domyślnie włączony jest domyślnie wyłączony extglob, aby nie zakłócał rozszerzenia, w dokumentach wyjaśnia, dlaczego tak jest. dopasuj wszystko, co zaczyna się na f z wyjątkiem foo: f! (oo), oczywiście „jedzenie” nadal będzie pasować (potrzebujesz f! (oo *), aby zatrzymać rzeczy zaczynające się na „foo” lub, jeśli chcesz się go pozbyć pewnych rzeczy kończących się na '.foo' użyj! ( .foo) lub prefiks: myprefix! ( .foo) (pasuje do myprefixBLAH, ale nie myprefixBLAH.foo)
osirisgothra
227

Opcja extglobpowłoki zapewnia mocniejsze dopasowanie wzorców w wierszu poleceń.

Włączasz za pomocą shopt -s extglobi wyłączasz za pomocą shopt -u extglob.

W twoim przykładzie początkowo zrobiłbyś:

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

Pełne dostępny ext zakończył glob operatorzy Bing są (fragment man bash):

Jeśli opcja powłoki extglob jest włączona przy użyciu wbudowanego shopt, rozpoznawanych jest kilka rozszerzonych operatorów dopasowywania wzorców. Lista wzorów jest listą jednego lub więcej wzorów oddzielonych | Wzory złożone mogą być tworzone przy użyciu jednego lub więcej następujących pod-wzorów:

  • ? (lista wzorców)
    Dopasowuje zero lub jedno wystąpienie podanych wzorów
  • * (lista wzorców)
    Dopasowuje zero lub więcej wystąpień podanych wzorów
  • + (lista wzorców)
    Dopasowuje jedno lub więcej wystąpień podanych wzorów
  • @ (lista wzorów)
    Dopasowuje jeden z podanych wzorów
  • ! (lista wzorców)
    Dopasowuje wszystko oprócz jednego z podanych wzorów

Na przykład, jeśli chcesz wyświetlić listę wszystkich plików w bieżącym katalogu, które nie są .club .hpliki, wykonaj następujące czynności:

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

Oczywiście działa normalne globowanie powłoki, więc ostatni przykład można również zapisać jako:

$ ls -d !(*.[ch])
tzot
źródło
1
Jaki jest powód -d?
Big McLargeHuge
2
@Koveras w przypadku, gdy jeden z plików .club .hjest katalogiem.
tzot
@DaveKennedy Jest to lista wszystkiego w bieżącym katalogu D, ale nie zawartość podkatalogów, które mogą się w nim znajdować D.
spurra
23

Nie w bashu (o którym wiem), ale:

cp `ls | grep -v Music` /target_directory

Wiem, że nie było to dokładnie to, czego szukałeś, ale to rozwiąże twój przykład.

ejgottl
źródło
Domyślnie ls umieści wiele plików w linii, co prawdopodobnie nie da właściwych rezultatów.
Daniel Bungert,
10
Tylko gdy stdout jest terminalem. Gdy jest używany w potoku, ls drukuje jedną nazwę pliku na linię.
Adam Rosenfield,
ls umieszcza tylko kilka plików w linii, jeśli jest wyprowadzany do terminala. Wypróbuj sam - „ls | less” nigdy nie będzie zawierać wielu plików w linii.
SpoonMeiser,
3
Nie będzie działać dla nazw plików zawierających spacje (lub inne białe znaki spacji).
tzot
7

Jeśli chcesz uniknąć kosztów memu korzystania z polecenia exec, uważam, że lepiej poradzisz sobie z xargs. Myślę, że poniższe jest bardziej wydajną alternatywą dla

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/
Steve
źródło
6

W bash alternatywą shopt -s extglobjest GLOBIGNOREzmienna . To nie jest naprawdę lepsze, ale łatwiej mi zapamiętać.

Przykładem może być to, czego chciał oryginalny plakat:

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

Po zakończeniu , unset GLOBIGNOREaby móc to zrobić rm *techno*w katalogu źródłowym.

mivk
źródło
5

Możesz także użyć dość prostej forpętli:

for f in `find . -not -name "*Music*"`
do
    cp $f /target/dir
done
mipadi
źródło
1
Dokonuje to rekurencyjnego znalezienia, które jest innym zachowaniem niż to, czego chce OP.
Adam Rosenfield,
1
używać -maxdepth 1do nierekurencyjnych?
avtomaton
Uważam, że jest to najczystsze rozwiązanie bez konieczności włączania / wyłączania opcji powłoki. Opcja -maxdepth byłaby zalecana w tym poście, aby uzyskać wynik wymagany przez PO, ale wszystko zależy od tego, co próbujesz osiągnąć.
David Lapointe
Używanie findw backtickach spowoduje nieprzyjemne działanie, jeśli znajdzie jakieś nietrywialne nazwy plików.
tripleee
5

Osobiście wolę używać grep i komendy while. Pozwala to pisać potężne, ale czytelne skrypty, zapewniając, że skończysz robić dokładnie to, co chcesz. Ponadto za pomocą polecenia echa można wykonać próbę przed rozpoczęciem rzeczywistej operacji. Na przykład:

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

wydrukuje pliki, które skończysz kopiować. Jeśli lista jest poprawna, następnym krokiem jest po prostu zastąpienie polecenia echo poleceniem kopiowania w następujący sposób:

ls | grep -v "Music" | while read filename
do
cp "$filename" /target_directory
done
Abid H. Mujtaba
źródło
1
Działa to tak długo, jak w nazwach plików nie ma tabulatorów, znaków nowej linii, więcej niż jednej spacji w wierszu ani odwrotnych ukośników. Chociaż są to przypadki patologiczne, dobrze jest mieć świadomość takiej możliwości. W bashmożna użyć while IFS='' read -r filename, ale wtedy nowe linie są nadal problemem. Zasadniczo najlepiej nie używać lsdo wyliczania plików; narzędzia takie findsą znacznie lepiej dostosowane.
Thedward
Bez dodatkowych narzędzi:for file in *; do case ${file} in (*Music*) ;; (*) cp "${file}" /target_directory ; echo ;; esac; done
Thedward
mywiki.wooledge.org/ParsingLs wymienia szereg dodatkowych powodów, dla których należy tego unikać.
tripleee
5

Sztuczka Nie widziałem tu jeszcze, że nie używa extglob, findczy grepjest w leczeniu dwie listy plików jak zestawy i „diff” je za pomocą comm:

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

commjest lepszy niż, diffponieważ nie ma dodatkowego cruft.

Zwraca wszystkie elementy zestawu 1 ls, które również nie znajdują się w zestawie 2 ls *Music*,. Wymaga to, aby oba zestawy były posortowane, aby działały poprawnie. Nie ma problemu lsi globalnej ekspansji, ale jeśli używasz czegoś podobnego find, pamiętaj, aby wywołać sort.

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

Potencjalnie przydatne.

James M. Lay
źródło
1
Jedną z korzyści wykluczenia jest nie przechodzenie przez katalog w pierwszej kolejności. To rozwiązanie wykonuje dwa przejścia do podkatalogów - jeden z wykluczeniem, a drugi bez.
Mark Stosberg,
Bardzo dobry punkt, @MarkStosberg. Chociaż jedną zaletą tej techniki jest to, że można odczytać wykluczenia z rzeczywistego pliku, np.comm -23 <(ls) exclude_these.list
James M. Lay
3

Jedno rozwiązanie tego można znaleźć w find.

$ mkdir foo bar
$ touch foo/a.txt foo/Music.txt
$ find foo -type f ! -name '*Music*' -exec cp {} bar \;
$ ls bar
a.txt

Znajdź ma wiele opcji, możesz uzyskać dość szczegółowe informacje na temat tego, co dołączasz i wykluczasz.

Edytuj: Adam w komentarzach zauważył, że jest to rekurencyjne. znajdź opcje mindepth i maxdepth mogą być przydatne w kontrolowaniu tego.

Daniel Bungert
źródło
To robi kopię rekurencyjną, która jest innym zachowaniem. Odradza także nowy proces dla każdego pliku, co może być bardzo nieefektywne w przypadku dużej liczby plików.
Adam Rosenfield,
Koszt odrodzenia procesu jest w przybliżeniu zerowy w porównaniu do wszystkich operacji we / wy, które generuje kopiowanie każdego pliku. Powiedziałbym więc, że to wystarcza do sporadycznego użycia.
dland
Niektóre obejścia tego procesu: stackoverflow.com/questions/186099/…
Vinko Vrsalovic,
użyj „-maxdepth 1”, aby uniknąć rekurencji.
ejgottl,
użyj wstecznego, aby uzyskać analogiczne rozszerzenie powłoki wieloznacznej powłoki: cp find -maxdepth 1 -not -name '*Music*'/
katalog_docelowy
2

Poniższe prace zawierają listę wszystkich *.txtplików w bieżącym katalogu, z wyjątkiem plików zaczynających się na liczbę.

To działa w bash, dash, zsha wszystkie inne kompatybilne muszle 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. W pierwszym wierszu wzorzec /some/dir/*.txtspowoduje foriterację pętli po wszystkich plikach, /some/dirktórych nazwa kończy się na .txt.

  2. W drugim wierszu zastosowano instrukcję case, aby usunąć niepożądane pliki. - ${FILE##*/}Wyrażenie usuwa wszystkie wiodące komponenty nazwy katalogu z nazwy pliku (tutaj /some/dir/), dzięki czemu wzory mogą pasować tylko do nazwy basena pliku. (Jeśli usuwasz tylko nazwy plików na podstawie sufiksów, możesz $FILEzamiast tego skrócić ).

  3. W linii trzeciej wszystkie pliki pasujące do casewzorca [0-9]*zostaną pominięte ( continueinstrukcja przeskoczy do następnej iteracji forpętli). - Jeśli chcesz, możesz zrobić coś bardziej interesującego, np. Pominąć wszystkie pliki, które nie zaczynają się na literę (a – z) [!a-z]*, lub możesz użyć wielu wzorców, aby pominąć kilka rodzajów nazw plików, np. [0-9]*|*.bakPominąć pliki oba .bakpliki oraz pliki, które nie zaczynają się od liczby.

zrajm
źródło
Doh! Wystąpił błąd (pasowałem do niego *.txtzamiast po prostu *). Naprawiono teraz.
zrajm
0

to zrobiłoby to wykluczając dokładnie „Muzykę”

cp -a ^'Music' /target

to i to za wykluczenie takich rzeczy jak Muzyka? * lub *? Muzyka

cp -a ^\*?'complete' /target
cp -a ^'complete'?\* /target
gabreal
źródło
Strona cppodręcznika w systemie MacOS ma -aopcję, ale robi coś zupełnie innego. Która platforma to obsługuje?
tripleee