bash, jeśli nie wiele warunków bez podpowłoki?


23

Chcę połączyć wiele warunków w instrukcji if powłoki i zanegować kombinację. Mam następujący kod roboczy dla prostej kombinacji warunków:

if [ -f file1 ] && [ -f file2 ] && [ -f file3 ] ; then
  # do stuff with the files
fi

To działa dobrze. Jeśli chcę to zanegować, mogę użyć następującego działającego kodu:

if ! ( [ -f file1 ] && [ -f file2 ] && [ -f file3 ] ) ; then
  echo "Error: You done goofed."
  exit 1
fi
# do stuff with the files

Działa to również zgodnie z oczekiwaniami. Jednak przychodzi mi do głowy, że nie wiem, co właściwie robią nawiasy. I chcą , aby ich używać tylko do grupowania, ale jest to faktycznie tarło podpowłoce? (Jak mogę to stwierdzić?) Jeśli tak, czy istnieje sposób na grupowanie warunków bez odradzania podpowłoki?


Przypuszczam, że mógłbym również użyć logiki logicznej, aby określić równoważne wyrażenie: if ! [ -f file1 ] || ! [ -f file 2 ] || ! [ -f file3 ] ; thenale chciałbym bardziej ogólnej odpowiedzi.
Wildcard,

Możesz również zanegować wewnątrz nawiasów klamrowych, np.if [[ ! -f file1 ]] && [[ ! -f file2 ]]; then
DopeGhoti

1
Tak, właśnie przetestowałem if [ ! 1 -eq 2 ] && [ ! 2 -eq 3 ]; then echo yep; fii działa. Po prostu zawsze piszę testy z podwójnymi nawiasami klamrowymi w ramach nawyku. Ponadto, aby upewnić się, że tak nie jest bash, przeprowadziłem dalsze testy if /bin/test ! 1 -eq 2 && /bin/test ! 2 -eq 3 ; then echo yep; fii tak też działa.
DopeGhoti,

1
@Wildcard - [ ! -e file/. ] && [ -r file ]spowoduje upuszczenie katalogów. neguj to, jak chcesz. oczywiście, że -dtak.
mikeserv

1
Powiązane pytanie (podobne, ale nie tak wiele dyskusji i odpowiedzi niezbyt pomocne): unix.stackexchange.com/q/156885/135943
Wildcard

Odpowiedzi:


32

Musisz użyć { list;}zamiast (list):

if ! { [ -f file1 ] && [ -f file2 ] && [ -f file3 ]; }; then
  : do something
fi

Oba są Poleceniami grupującymi , ale { list;}wykonują polecenia w bieżącym środowisku powłoki.

Zauważ, że ;in { list;}jest potrzebne do rozdzielenia listy od }odwrotnego słowa, możesz również użyć innego separatora. Wymagane {jest również spacja (lub inny separator) po .


Dziękuję Ci! Coś, co na początku mnie zaskoczyło (ponieważ używam nawiasów klamrowych awkczęściej niż w bash), to potrzeba spacji po otwartym nawiasie klamrowym. Wspominasz „możesz również użyć innego ogranicznika”; jakieś przykłady?
Wildcard,

@Wildcard: Tak, potrzebna jest biała spacja po otwartym nawiasie klamrowym. Przykładem innego ogranicznika jest nowa linia, ponieważ często chcesz zdefiniować funkcję.
cuonglm,

@don_crissti: W zsh, możesz użyć białych znaków
cuonglm

@don_crissti: &jest to także separator, którego można użyć { echo 1;:&}, można również użyć wielu znaków nowej linii.
cuonglm,

2
Można użyć dowolnego specjalny znak cytat z instrukcji they must be separated from list by whitespace or another shell metacharacter.W bash one: metacharacter: | & ; ( ) < > space tab . W przypadku tego konkretnego zadania uważam, że którekolwiek z & ; ) space tabnich zadziała. .... .... Z zsh (jako komentarz) each sublist is terminated by &', &!', or a newline.

8

Możesz w pełni wykorzystać tę testfunkcjonalność, aby osiągnąć to, czego chcesz. Ze strony podręcznika użytkownika test:

 ! expression  True if expression is false.
 expression1 -a expression2
               True if both expression1 and expression2 are true.
 expression1 -o expression2
               True if either expression1 or expression2 are true.
 (expression)  True if expression is true.

Twój stan może wyglądać następująco:

if [ -f file1 -a -f file2 -a -f file3 ] ; then
    # do stuff with the files
fi

W celu zanegowania użyj nawiasów ucieczkowych:

if [ ! \( -f file1 -a -f file2 -a -f file3 \) ] ; then
    echo "Error: You done goofed."
    exit 1
fi
# do stuff with the files

6

Aby przenośnie zanegować złożony warunek w skorupce, musisz albo zastosować prawo De Morgana i wcisnąć negację do samego końca [połączeń ...

if [ ! -f file1 ] || [ ! -f file2 ] || [ ! -f file3 ]
then
    # do stuff
fi

... lub musisz użyć then :; else...

if [ -f file1 ] && [ -f file2 ] && [ -f file3 ]
then :
else
  # do stuff
fi

if ! commandnie jest przenośny i nie jest [[.

Jeśli nie potrzebujesz całkowitej przenośności, nie pisz skryptu powłoki . W rzeczywistości istnieje większe prawdopodobieństwo, że znajdziesz /usr/bin/perlna losowo wybranym Uniksie bash.


1
!jest POSIX, który obecnie jest wystarczająco przenośny. Nawet systemy, które nadal są dostarczane z powłoką Bourne'a, mają również POSIX sh w innym systemie plików, którego można użyć do interpretacji standardowej składni.
Stéphane Chazelas,

@ StéphaneChazelas Jest to technicznie prawdą, ale z mojego doświadczenia łatwiej jest przepisać skrypt w Perlu, niż radzić sobie z kłopotami z lokalizowaniem i aktywowaniem środowiska powłoki zgodnego z POSIX, poczynając od /bin/sh. Skrypty Autoconf działają tak, jak sugerujesz, ale myślę, że wszyscy wiemy, jak mało to poparcia.
zwolnienie

5

inni zauważyli {złożone ;}grupowanie poleceń , ale jeśli wykonujesz identyczne testy na zestawie, możesz użyć innego rodzaju:

if  ! for f in file1 file2 file3
      do  [ -f "$f" ] || ! break
      done
then  : do stuff
fi

... jak pokazano gdzie indziej { :;}, nie ma trudności z zagnieżdżaniem poleceń złożonych ...

Zauważ, że powyższe (zwykle) testy zwykłych plików. Jeśli szukasz tylko istniejących , czytelnych plików, które nie są katalogami:

if  ! for f in file1 file2 file3
      do  [ ! -d "$f" ] && 
          [   -r "$f" ] || ! break
      done
then  : do stuff
fi

Jeśli nie obchodzi Cię, czy są to katalogi, czy nie:

if  ! command <file1 <file2 <file3
then  : do stuff
fi

... działa dla każdego czytelnego , dostępnego pliku, ale prawdopodobnie zawiesi się dla programistów fifos bez / z.


2

To nie jest pełna odpowiedź na twoje główne pytanie, ale zauważyłem, że w komentarzu wspominasz o testach złożonych (plik czytelny); na przykład,

if [ -f file1 ] && [ -r file1 ] && [ -f file2 ] && [ -r file2 ] && [ -f file3 ] && [ -r file3 ]

Możesz to trochę skonsolidować, definiując funkcję powłoki; na przykład,

readable_file()
{
    [ -f "$1" ]  &&  [ -r "$1" ]
}

Dodaj obsługę błędów (np. [ $# = 1 ]) Do smaku. Pierwsze ifstwierdzenie powyżej można teraz skondensować

if readable_file file1  &&  readable_file file2  &&  readable_file file3

i możesz to jeszcze bardziej skrócić, skracając nazwę funkcji. Podobnie, możesz zdefiniować not_readable_file()(lub nrfw skrócie) i dołączyć negację do funkcji.


Fajnie, ale dlaczego nie dodać pętli? readable_files() { [ $# -eq 0 ] && return 1 ; for file in "$@" ; do [ -f "$file" ] && [ -r "$file" ] || return 1 ; done ; } (Zastrzeżenie: Testowałem ten kod i działa, ale nie był to jednowierszowy. Dodałem średniki do zamieszczania tutaj, ale nie przetestowałem ich umiejscowienia.)
Wildcard

1
Słuszna uwaga. Chciałbym podnieść niewielką obawę, że ukrywa ona więcej funkcji logicznej kontroli (koniunkcji) w funkcji; ktoś, kto czyta scenariusz i widzi, if readable_files file1 file2 file3nie wiemy , czy funkcja robi AND lub OR - chociaż (a) Myślę, że to dość intuicyjne, (b) jeśli nie jesteś dystrybucją skryptu, to wystarczająco dobre, jeśli masz go zrozumieć, oraz (c) ponieważ zasugerowałem skrócenie nazwy funkcji w zapomnienie, nie jestem w stanie mówić o czytelności. … (Ciąg dalszy)
G-Man mówi „Przywróć Monikę”

1
(Ciąg dalszy) ... BTW, funkcja jest w porządku, tak, jak to napisał, ale można zastąpić for file in "$@" ; doz for file do- Domyślnie jest to in "$@", i (nieco wbrew intuicji) ;jest nie tylko niepotrzebna, ale faktycznie zniechęca.
G-Man mówi „Przywróć Monikę”

0

Możesz również zanegować wewnątrz testów nawiasów, aby ponownie użyć oryginalnego kodu:

if [[ ! -f file1 ]] || [[ ! -f file2 ]] || [[ ! -f file3 ]] ; then
  # do stuff with the files
fi

2
Potrzebujesz ||zamiast&&
cuonglm

W teście nie ma „nie ma żadnych plików”, w teście „ nie ma żadnych plików”. Użycie ||spowoduje wykonanie predykatu, jeśli któryś z plików nie będzie obecny, nie tylko wtedy, gdy tylko wszystkie pliki nie będą obecne. NOT (A AND B AND C) jest taki sam jak (NOT A) AND (NOT B) AND (NOT C); zobacz oryginalne pytanie.
DopeGhoti,

@DopeGhoti, cuonglm ma rację. Jest tak, jeśli nie (wszystkie pliki), więc dzieląc go w ten sposób, potrzebujesz ||zamiast &&. Zobacz mój pierwszy komentarz poniżej samego pytania.
Wildcard,

2
@DopeGhoti: not (a and b)jest taki sam jak (not a) or (not b). Zobacz en.wikipedia.org/wiki/De_Morgan%27s_laws
cuonglm

1
To musi być czwartek. Nigdy nie mogłem zrozumieć czwartków. Poprawiony, a ja zostawiam swoje usta w usta dla potomności.
DopeGhoti,

-1

Chcę ich używać tylko do grupowania, ale czy tak naprawdę tworzy spawarkę? (Jak mogę powiedzieć?)

Tak, polecenia wewnątrz (...)są uruchamiane w podpowłoce.

Aby sprawdzić, czy jesteś w podpowłoce, możesz porównać PID bieżącej powłoki z PID interaktywnej powłoki.

echo $BASHPID; (echo $BASHPID); 

Przykładowe dane wyjściowe pokazujące, że nawiasy odradzają podpowłokę, a nawiasy klamrowe nie:

$ echo $BASHPID; (echo $BASHPID); { echo $BASHPID;}
50827
50843
50827
$ 

6
()spawnuje {}
skorupę

@ heemayl, to druga część mojego pytania - czy jest jakiś sposób, aby zobaczyć lub zademonstrować na ekranie fakt pojawienia się podpowłoki? (To może być osobne pytanie, ale dobrze byłoby o tym wiedzieć.)
Wildcard,

1
@heemayl Masz rację! Zrobiłem to, echo $$ && ( echo $$ )aby porównać PID i były takie same, ale tuż przed przesłaniem komentarza z informacją, że się mylisz, spróbowałem jeszcze jednej rzeczy. echo $BASHPID && ( echo $BASHPID )daje różne PID
David King

1
@ heemayl echo $BASHPID && { echo $BASHPID; }podaj ten sam PID, jak sugerowałbyś. Dzięki za edukację
David King

@DavidKing, dzięki za użycie komend testowych $BASHPID, to kolejna rzecz, której mi brakowało.
Wildcard,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.