Wyklucz z * w linii poleceń

14

Istnieje wiele sytuacji, w których użycie a *jest praktycznie nieuniknione - np. rm -rf *W folderze zawierającym tysiące podfolderów i plików.

Ale co, jeśli chcesz wykluczyć tylko jeden lub dwa pliki lub foldery z rmpolecenia? Przeszukiwałem swoją stronę i znalazłem tylko dość skomplikowane rozwiązania, jak find . -depth -not \( -name 'one' -o -name 'two' \ -o -name 'three' \) -exec rm {} \;podano tutaj .

Czy można to zrobić w łatwiejszy sposób - bez tego objazdu find? Np. rm -rf --exclude='one' --exclude='two' --exclude='three' *Jak w rsync czy po prostu rm -rf -e 'one','two','three' *?

Może nawet ogóle możliwość wyłączenia rzeczy z *(tak jak inne polecenia cp, mv... nie trzeba zaimplementować własną rękę)? Coś w *{'one','two','three'}tym stylu?

David
źródło
3
Jeśli chcesz zachować tylko jeden lub dwa pliki, najprostszym i najłatwiejszym sposobem byłoby przeniesienie tych plików do innej lokalizacji, wyczyszczenie wszystkich pozostałych plików i przeniesienie tych, które zatrzymałeś. Jeśli chcesz zachować o wiele więcej plików, skorzystałbym findz tej --deleteopcji (nie trzeba wykonywać rmdla każdego pliku. To niepotrzebny narzut).
Hennes,
To byłaby możliwość, ale jest prawie tak skomplikowana jak ta, o której wspomniałem. Wiem, że mógłbym połączyć polecenia z czymś takim mv -t /tmp one two three && rm -rf * && mv -t . /tmp/one /tmp/two /tmp/three, ale wolałbym rozwiązanie dające możliwość wyraźnego wykluczenia czegoś z *. Z pewnością będą sytuacje, w których przenoszenie lub kopiowanie plików do innego miejsca docelowego nie będzie możliwe.
David,
2
Właśnie dlatego nie opublikowałem tego jako odpowiedzi. Zwykle jako mniej złożony sposób robienia rzeczy (trzy bardzo proste polecenia vs jedno nieco bardziej złożone). Nienawidzę złożoności w połączeniu z rm.
Hennes,

Odpowiedzi:

33

W przypadku Bash dostępna jest opcja powłoki, extglobktóra jest domyślnie wyłączona, aby zachować zgodność ze standardową składnią powłoki. Extglob uzupełnia składnię dodatkowymi operatorów takich jak !(), ?(), @()i kilka innych.

Aby włączyć extglob, wpisz shopt -s extglob. Aby pozostawić włączone dla bieżącego typu użytkownika echo 'shopt -s extglob' >> ~/.bashrc.

Na przykład rm: Z extglobmożna użyć

rm -rf !(one|two|three)
choroba
źródło
Wydaje się, że to dobre rozwiązanie, ale nie chcę go włączać i wyłączać za każdym razem, gdy wpadam w sytuację taką jak opisana.
David,
3
@David: możesz włożyć shoptcoś do siebie ~/.bashrc, to nie zaszkodzi.
enzotib,
Jeśli dobrze to zrozumiem, opcja extglob wcale nie zmienia efektu *, ale dodaje podobne wskaźniki (takie jak !), które mają dodatkowe funkcje? W takim przypadku byłoby to cudowne rozwiązanie.
David,
1
@David: Dokładnie.
choroba
1
@David Aby zachować zgodność ze standardową składnią powłoki . Prawdopodobnie istnieją inne sytuacje, w których zachowanie może się zmienić.
gerrit
4

Zbudowałem z wyjątkiem tego (proszę, że nie byłem jeszcze w stanie dodać dokumentacji).

Mam folder podobny do następującego:

$ ls
a b c d

Pisanie except b dda ci aic

$ except b d
ac

Teraz możesz to potokować rm.

except b d | xargs -0 rm

Może to być trudne do zapamiętania, więc dlaczego po prostu nie zbudować skryptu oprócz wyjątku o nazwie rm-except:

#!/usr/bin/env bash

except "$@" | xargs -0 rm

Podobnie łatwe jest ls-except:

#!/usr/bin/env bash

except "$@" | xargs -0 ls
Metoda pomocnicza
źródło
3
Jest to miłe, ale tak naprawdę nie potrzebne, gdy wiesz o extglob - askubuntu.com/a/329233/138369
David
1

Jako alternatywę dla extglob (choć jest to bardzo dobra odpowiedź i każdy powinien mieć shopt -s extglob globstarw swoim .bashrc), możesz użyć zmiennej globalnej $ GLOBIGNORE . Powiedzmy, że chcesz pobrać każdy plik oprócz „foo.txt” i „bar baz.txt”:

GLOBIGNORE=foo.txt:'bar baz.txt'

... spowoduje to jednak włączenie opcji powłoki dotglob, co oznacza, że *dopasuje pliki zaczynające się od kropki (które zwykle są ukryte). Tak naprawdę potrzebujesz dwóch poleceń:

GLOBIGNORE=foo.txt:'bar baz.txt'
shopt -u dotglob

Ponieważ jest to zmienna globalna, wpłynie na każdy używany glob, dopóki $ GLOBSTAR nie zostanie rozbrojony przez wylogowanie lub za pomocą

GLOBIGNORE=

Działa także tylko na ciągach literalnych przekazywanych do zmiennej. Możesz zobaczyć, co mam na myśli, ustawiając $ GLOBIGNORE i patrząc na różnicę między tymi poleceniami:

printf '%s\n' *
printf '%s\n' ./*
zła
źródło