Jakie jest ustawienie bash dla globowania, aby kontrolować, czy * pasuje do plików kropek

12

Ostatnio byłem zaskoczony, gdy zrobiłem coś podobnego mv ./* ../somedirectory i stwierdziłem, że podobne pliki .gitignorenie zostały przeniesione.

Większość pracy wykonuję w Zsh na OS X, a ta niespodzianka ugryzła mnie w bicie na CentOS. Próbowałem bash na OS X i znalazłem to samo zachowanie: *nie pasuje do plików kropek. Wydaje mi się to bardzo niepożądane, ale najwyraźniej jest to domyślne ustawienie bash. (To może być domyślna wartość zsh dla wszystkich, które pamiętam, ale mogłem ją zmienić wiele lat temu w moim .zshrc i zapomniałem, że kiedykolwiek działał inaczej.)

Jak mogę skonfigurować bash, aby zachowywał się tak, jak się spodziewałem: aby * pasował do wszystkich plików i nie ignorował plików kropek.

W przypadku, gdy jest to w ogóle niejasne, oto jak go odtworzyć

cd /tmp
mkdir {t,d}est
touch test/{.,}{1,2,3,4,5,6,7}
ls -hal test
mv test/* dest
ls -hal test     # notice dot files are still there
ls -hal dest     # notice only some files were mv'ed
ikonoklasta
źródło
możliwy duplikat Jak przenieść wszystkie pliki (w tym ukryte) z katalogu do innego?
Gilles „SO- przestań być zły”
Tak, są spokrewnieni. Nie pojawił się, kiedy szukałem (prawdopodobnie dlatego, że pytający o to nie wspominał o plikach globowania lub kropkach), ale to naprawdę inne pytanie. Pytał, jak przenieść pliki, a ja pytałem, jak zmienić zachowanie powłoki, a zwłaszcza bash. Mogłem nie zapytać, czy to pytanie mi się pojawiło (ponieważ twoja niezwykle kompletna i dokładna odpowiedź zawiera ustawienie bash), ale wciąż jest to inne pytanie, a ktoś, kto szuka odpowiedzi na moje pytanie, niekoniecznie znajdzie jego pytanie.
iconoclast
Ten cały „ktoś, kto szuka odpowiedzi na moje pytanie niekoniecznie znajdzie swoje pytanie”, właśnie dlatego mamy taki sposób zamykania pytań jako duplikatów.
Gilles 'SO - przestań być zły'
tak, ale wydaje się, że brakuje ci głównej kwestii, a mianowicie, że jest to inne pytanie .
iconoclast

Odpowiedzi:

14

Grzmotnąć

Jak już zauważyłeś, bash nie będzie pasował do .znaku na początku nazwy lub ukośnika. Aby zmienić dopasowanie kropki, musisz ustawić dotglobopcję - man bash :

dotglob Jeśli ustawione, bash zawiera nazwy plików rozpoczynające się od `. ' w
    wyniki rozwinięcia nazwy ścieżki.

Aby włączyć / ustawić za pomocą bash shopt, np .:

shopt -s dotglob


W przypadku zsh możesz również użyć tej dotglobopcji, ale będziesz musiał użyć setoptjej, np .:

setopt dotglob
Ulrich Dangel
źródło
2
Dzięki zsh, lepszym rozwiązaniem jest włączenie dotglob dla poszczególnych mv test/*(D) /dest
globów
7

Testowałem to i to rozwiązuje problem:

shopt -s dotglob

Wynik:

~/stackexchangeanswers/40662$ ls -hal dest
total 8.0K
drwxr-xr-x 2 jodiec jodiec 4.0K 2012-06-12 22:15 .
drwxr-xr-x 4 jodiec jodiec 4.0K 2012-06-12 22:15 ..
-rw-r--r-- 1 jodiec jodiec    0 2012-06-12 22:15 1
-rw-r--r-- 1 jodiec jodiec    0 2012-06-12 22:15 .1
-rw-r--r-- 1 jodiec jodiec    0 2012-06-12 22:15 2
-rw-r--r-- 1 jodiec jodiec    0 2012-06-12 22:15 .2
-rw-r--r-- 1 jodiec jodiec    0 2012-06-12 22:15 3
-rw-r--r-- 1 jodiec jodiec    0 2012-06-12 22:15 .3
...snipped....
Jodie C.
źródło
Przepraszam, ale Ulrich odpowiedział pierwszy.
iconoclast
3
Wszyscy jesteśmy w tym samym zespole. W porządku.
Jodie C
6

*to glob rozwinięty przez powłokę. Domyślnie powłoki nie zawierają plików, których nazwa zaczyna się od .(zwanych plikami ukrytymi lub plikami dotfiles), chyba że wiodące hasło .jest wprowadzone dosłownie.

*lub [.]*lub ?*lub *.*lub dir/*nie będzie zawierać plików kropkowych.

.*lub dir/.*będzie.

Więc możesz zrobić:

mv -- * .* /dest/

jednak niektóre powłoki, w tym bash(ale nie zsh, mkshani nie fish), mają tę wadę, że rozszerzenie .*zawiera .i ..specjalne pozycje katalogu, których tu nie chcesz (i generalnie nigdy nie chcesz, aby glob zawierał, dlatego nazywam to błędem).

Z tego powodu okaże się, że czasami ludzie używają (w powłokach podobnych do Bourne'a):

mv -- * .[!.]* ..?* /dest/

To trzy globusy, pierwszy pasujący do nie ukrytych plików, drugi z nazwami plików rozpoczynającymi się od, .po którym następuje znak inny niż, .a trzeci z nazwami plików rozpoczynającymi się od, ..po którym następuje co najmniej jeden znak.

Jednak niektóre nowoczesne muszle mają na to lepsze sposoby

zsh

Za zshpomocą (D)kwalifikatora glob można określić, że glob powinien zawierać pliki kropkowe:

mv -- *(D) /dest/

zshnaprawiono również tę inną wadę powłoki Bourne'a, ponieważ jeśli wzorzec nie pasuje, mvpolecenie nie jest uruchamiane.

Jak powiedziano powyżej, nigdy też nie będzie zawierać .ani ..w swoich globach, więc

mv -- * .* /dest/

będzie bezpieczny. Jeśli jednak nie ma pasującego pliku *lub żaden plik nie pasuje, .*polecenie zostanie przerwane, więc lepiej użyć:

mv -- (*|.*) /dest/

Podobnie jak w niektórych innych powłokach, możesz również zmusić wszystkie globusy do dołączania plików dot (na przykład, jeśli chcesz, aby pliki dot były dołączane częściej niż nie):

setopt dotglob

lub:

set -o dotglob

Następnie, jeśli chcesz, aby określony glob nie zawierał plików dot, możesz go napisać:

echo *(^D)

Lub:

echo [^.]*

Grzmotnąć

Niestety bashnie ma globalnych kwalifikatorów. Pozostaje Ci zatem włączenie globalnego dodawania plików. W bashskładni jest:

shopt -s dotglob

(i używać [^.]*do globów bez ukrytych plików).

Z dotglob, bashnie obejmuje .ani ..w globs takich jak *, ale nadal robi to dla takich globów .*.

Jeśli ustawisz GLOBIGNOREzmienną na coś niepustego, wówczas automatycznie włącza dotglobopcję i wyklucza .i ..z .*globusów, ale nie z globusów dir/.*lub .*/filejedynek (!), Dzięki czemu zabezpieczenie jest całkiem bezużyteczne. Mógłbyś zrobić, GLOBIGNORE='*/.:*/..:./*:../*:*/./*:*/../*'ale wtedy zniszczyłoby globusy takie jak */.lub ./*lub ../*.

Lepszym obejściem jest użycie [.]*lub dir/[.]*lub [.]*/file(z dotglobwłączonym) do rozwijania plików dot, z wyjątkiem .i ...

ryba

fishglobusy nie obejmują .ani ... Gdy nie ma dopasowania, w zależności od wersji, będzie działać jak zsh(lub bash -o failglob) lub bash -o nullglob.

mv -- * .* /dest/

Działa, jeśli istnieją zarówno pliki ukryte, jak i nie ukryte. W przeciwnym razie YMMV i w niektórych wersjach może wywoływać, mv -- /destjeśli w ogóle nie ma pliku.

ksh93

W ksh93żadnym z nich nie ma kwalifikatora glob . Możesz dołączać pliki kropkowe do globów za pomocą:

FIGNORE='@(.|..)'

W przeciwieństwie do bashtych GLOBIGNORE, jest to zrobione poprawnie, a także rozwiązuje problem .*włączenia .i ...

yash

yashposiada dot-globopcję ( set -o dot-glob), ale wbrew temu bash, ekspansje glob (nawet z *) to .i ..tak jest całkiem bezużyteczny.

tcsh

set globdot

Działa jak w bash, to jest *to kropka pliki wyjątkiem .a ..jednak .*wciąż zawiera .i ..(i można użyć [.]*, aby rozwinąć ukryte pliki z wyjątkiem .i ..).

Stéphane Chazelas
źródło
4

Najprostszym sposobem na wydrukowanie pasujących plików kropek w bash jest ignorowanie .i ..:

$ GLOBIGNORE=/+/
$ printf '%s\n' *


Testować:

$ cd tmp; mkdir empty; cd empty; touch {,.}{a..h}
$ GLOBIGNORE=/+/
$  ls *
a  .a  b  .b  c  .c  d  .d  e  .e  f  .f  g  .g  h  .h

Wydrukował wszystkie pliki z kropkami lub nie, z godnym uwagi wyjątkiem .i ...

Twój przykład będzie również działał poprawnie:

$ cd /tmp; mkdir {t,d}est; touch test/{,.}{a..h}
$ mv test/* dest/
$ ls -a test
.  ..
$ ls -a test/*
ls: cannot access test/*: No such file or directory

Brak ważnych plików.
Pliki systemowe . ..nadal istnieją, ale rozszerzenie powłoki (używając *) ich nie uwzględni.

A docelowy katalog usuwające zawiera wszystkie pliki:

$ cd dest
$ ls -a *
a  .a  b  .b  c  .c  d  .d  e  .e  f  .f  g  .g  h  .h

Dlaczego?

To działa, ponieważ ustawienie GLOBIGNORE ma to niepożądane efekty:

  • Ustaw dotglob ( shopt -s dotglob) na matematyczne pliki kropek przy rozszerzeniach.
  • Nazwy plików . i ..są ignorowane, gdy jest ustawiony GLOBIGNORE, a nie jest pusty.

Szczegóły w instrukcji: LESS=+'/The GLOBIGNORE' man bash.

Musimy także ustawić zmienną GLOBIGNORE tak, aby zawierała nazwę, której żadna nazwa pliku nie mogłaby się równać:
ukośnik (i coś innego tylko jako podwójne zabezpieczenie).

$ GLOBIGNORE=/+/

Przydatne może być również ustawienie nullglob, jeśli jest to konieczne, aby uniknąć sytuacji, *gdy nie ma pliku pasującego do *.


źródło
0

Bardzo interesuje to ustawienie GLOBIGNORE = / + / @ user79743

Z mojego doświadczenia wynika, że rozbrajanie dotglob jest złym biznesem dla prawie wszystkich twoich poleceń (cp, mv itp.) ALE to samo dotyczy ustawiania nullglob (szczególnie z wyrażeniami regularnymi, które następnie wymagają ucieczki!).

Niemniej jednak, jeśli musisz je ustawić na początku programu, ale ingerujesz w określone części, w których wywołujesz polecenia takie jak cp, mv itp. Lub używasz wyrażeń regularnych, po prostu zrób to:

  # set dotglob, unset nullglob AFTER this
  is_nullglob=$( shopt -s | egrep -i '.*nullglob' )
  is_dotglob=$( shopt -s | egrep -i '.*dotglob' )
  [[ $is_nullglob ]] && shopt -u nullglob
  [[ ! $is_dotglob ]] && shopt -s dotglob
  # ... call commands ...
  # .....................
  # reset dotglob, nullglob to their previous values
  [[ $is_nullglob ]] && shopt -s nullglob
  [[ ! $is_dotglob ]] && shopt -u dotglob
centurian
źródło