bash se non più condizioni senza subshell?


23

Voglio combinare più condizioni in un'istruzione shell e annullare la combinazione. Ho il seguente codice di lavoro per una semplice combinazione di condizioni:

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

Funziona benissimo. Se voglio negarlo, posso usare il seguente codice di lavoro:

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

Funziona anche come previsto. Tuttavia, mi viene in mente che non so cosa stanno effettivamente facendo le parentesi lì. Io voglio usarli solo per il raggruppamento, ma è in realtà la deposizione delle uova una subshell? (Come posso dirlo?) In tal caso, esiste un modo per raggruppare le condizioni senza generare una subshell?


Suppongo che potrei anche usare la logica booleana per determinare l'espressione equivalente: if ! [ -f file1 ] || ! [ -f file 2 ] || ! [ -f file3 ] ; thenma vorrei una risposta più generale.
Wildcard il

Puoi anche negare all'interno delle parentesi graffe, ad esempioif [[ ! -f file1 ]] && [[ ! -f file2 ]]; then
DopeGhoti

1
Sì, ho appena testato if [ ! 1 -eq 2 ] && [ ! 2 -eq 3 ]; then echo yep; fie funziona. Scrivo sempre sempre test con doppie parentesi graffe. Inoltre, per garantire che non lo sia bash, ho ulteriormente testato if /bin/test ! 1 -eq 2 && /bin/test ! 2 -eq 3 ; then echo yep; fie funziona anche in questo modo.
DopeGhoti,

1
@Wildcard - [ ! -e file/. ] && [ -r file ]lascerà cadere le directory. negalo come preferisci. certo, ecco cosa -dfa.
Mikeserv,

1
Domanda correlata (discussione simile ma non così tanto e risposte non così utili): unix.stackexchange.com/q/156885/135943
Wildcard

Risposte:


32

Devi usare { list;}invece di (list):

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

Entrambi sono comandi di raggruppamento , ma { list;}eseguono comandi nell'attuale ambiente di shell.

Si noti che, ;in { list;}è necessario per delimitare l'elenco dalla }parola inversa, è possibile utilizzare anche altri delimitatori. È richiesto anche lo spazio (o altro delimitatore) successivo {.


Grazie! Qualcosa che mi ha inciampato all'inizio (dal momento che uso parentesi graffe awkpiù spesso che in bash) è la necessità di spazi bianchi dopo la parentesi graffa aperta. Citi "puoi usare anche altri delimitatori"; qualche esempio?
Wildcard il

@Wildcard: Sì, è necessario lo spazio bianco dopo la parentesi graffa aperta. Un esempio per altri delimitatori è una nuova riga, poiché spesso si desidera definire una funzione.
cuonglm,

@don_crissti: In zsh, puoi usare gli spazi bianchi
cuonglm il

@don_crissti: &è anche un separatore, puoi usare { echo 1;:&}, puoi usare anche più newline.
cuonglm,

2
È possibile utilizzare qualsiasi citazione metacarattere da manuale they must be separated from list by whitespace or another shell metacharacter.In bash sono: metacharacter: | & ; ( ) < > space tab . Per questo compito specifico, credo che qualcuno & ; ) space tabfunzionerà. .... .... Da zsh (come commento) each sublist is terminated by &', &!', or a newline.

8

È possibile utilizzare interamente la testfunzionalità per ottenere ciò che si desidera. Dalla pagina man di 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.

Quindi la tua condizione potrebbe apparire come:

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

Per negare l'uso di parentesi sfuggite:

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

6

Per negare in modo portabile un condizionale complesso nella shell, è necessario applicare la legge di De Morgan e spingere la negazione fino in fondo all'interno delle [chiamate ...

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

... oppure devi usare then :; else...

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

if ! commandnon è disponibile in modo portabile e nemmeno lo è [[.

Se non hai bisogno di portabilità totale, non scrivere uno script di shell . In realtà hai più probabilità di trovare /usr/bin/perlun Unix selezionato casualmente di quello che sei bash.


1
!è POSIX che al giorno d'oggi è abbastanza portatile. Anche i sistemi che vengono ancora forniti con la shell Bourne hanno anche un file POSIX da qualche altra parte nel filesystem che puoi usare per interpretare la tua sintassi standard.
Stéphane Chazelas,

@ StéphaneChazelas Questo è tecnicamente vero, ma nella mia esperienza è più semplice riscrivere uno script in Perl piuttosto che affrontare la seccatura di individuare e attivare l'ambiente shell conforme a POSIX a partire da /bin/sh. Gli script di autoconfigurazione fanno ciò che suggerisci, ma penso che tutti sappiamo quanto poco sia un avallo.
zwol,

5

altri hanno notato il raggruppamento dei {comandi composti ;}, ma se si eseguono test identici su un set, è possibile che si desideri utilizzare un tipo diverso:

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

... come è dimostrato altrove { :;}, non vi sono difficoltà nel nidificare i comandi composti ...

Si noti che quanto sopra (in genere) verifica i file regolari . Se stai cercando solo file esistenti e leggibili che non siano directory:

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

Se non ti importa se sono directory o no:

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

... funziona per qualsiasi file leggibile e accessibile , ma probabilmente si bloccherà per i cinquanta senza scrittori.


2

Questa non è una risposta completa alla tua domanda principale, ma ho notato che in un commento hai menzionato il test composto (file leggibile); per esempio,

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

Puoi consolidarlo un po 'definendo una funzione di shell; per esempio,

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

Aggiungi la gestione degli errori a (ad es. [ $# = 1 ]) A piacere. La prima ifaffermazione, sopra, ora può essere condensata

if readable_file file1  &&  readable_file file2  &&  readable_file file3

e puoi accorciarlo ulteriormente accorciando il nome della funzione. Allo stesso modo, è possibile definire not_readable_file()(o nrfin breve) e includere la negazione nella funzione.


Bello, ma perché non aggiungere semplicemente un loop? readable_files() { [ $# -eq 0 ] && return 1 ; for file in "$@" ; do [ -f "$file" ] && [ -r "$file" ] || return 1 ; done ; } (Dichiarazione di non responsabilità: ho testato questo codice e funziona ma non era un singolo punto. Ho aggiunto punti e virgola per la pubblicazione qui ma non ho verificato il loro posizionamento.)
Wildcard il

1
Buon punto. Vorrei sollevare la preoccupazione minore che nasconda maggiormente la logica di controllo (congiunzione) nella funzione; qualcuno che legge lo script e vede if readable_files file1 file2 file3non saprebbe se la funzione stava eseguendo AND o OR - sebbene (a) immagino sia abbastanza intuitivo, (b) se non stai distribuendo lo script, è abbastanza buono se lo capisci, e (c) poiché ho suggerito di abbreviare il nome della funzione in oscurità, non sono in grado di parlare di leggibilità. ... (proseguendo)
G-Man dice "Reinstate Monica" il

1
(Proseguendo) ... A proposito, la funzione va bene nel modo in cui l'hai scritta, ma puoi sostituirla for file in "$@" ; docon for file do: per impostazione predefinita in "$@", e (in qualche modo contro-intuitivo) il; non è solo superflua ma effettivamente scoraggiata.
G-Man dice 'Reinstate Monica' il

0

Puoi annullare anche i test di parentesi graffa, quindi per riutilizzare il codice originale:

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

2
È necessario ||invece&&
cuonglm il

Il test non è "sono qualsiasi dei file non sono qui", il test è "sono nessuno dei file lì". L'utilizzo ||eseguirà il predicato se uno dei file non è presente, non se e solo se tutti i file non sono presenti. NOT (A AND B AND C) è uguale a (NOT A) AND (NOT B) AND (NOT C); vedi la domanda originale.
DopeGhoti,

@DopeGhoti, cuonglm ha ragione. In caso contrario (tutti i file presenti), quindi quando lo dividi in questo modo devi ||invece &&. Vedi il mio primo commento sotto la mia stessa domanda.
Wildcard il

2
@DopeGhoti: not (a and b)è lo stesso di (not a) or (not b). Vedi en.wikipedia.org/wiki/De_Morgan%27s_laws
cuonglm,

1
Deve essere giovedì. Non sono mai riuscito a capire il giovedì. Corretto, e lascerò il mio piede in bocca per i posteri.
DopeGhoti,

-1

Voglio usarli solo per il raggruppamento, ma in realtà sta generando una subshell? (Come posso dirlo?)

Sì, i comandi all'interno di (...)vengono eseguiti in una subshell.

Per verificare se ci si trova in una subshell è possibile confrontare il PID della shell corrente con il PID della shell interattiva.

echo $BASHPID; (echo $BASHPID); 

Esempio di output, che dimostra che le parentesi generano una sottostruttura e le parentesi graffe non:

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

6
()fa spa subshell {}... forse intendevi ...
heemayl

@heemayl, questa è l'altra parte della mia domanda: c'è un modo in cui posso vedere o dimostrare sul mio schermo il fatto che viene generata una subshell? (Potrebbe davvero essere una domanda separata, ma sarebbe bene saperlo qui.)
Wildcard il

1
@heemayl Hai ragione! Avevo fatto il echo $$ && ( echo $$ )confronto tra i PID ed erano gli stessi, ma poco prima di inviare il commento dicendoti che avevi torto ho provato un'altra cosa. echo $BASHPID && ( echo $BASHPID )dà diversi PID
David King il

1
@heemayl echo $BASHPID && { echo $BASHPID; }fornisce lo stesso PID che hai suggerito sarebbe il caso. Grazie per l'educazione
David King,

@DavidKing, grazie per i comandi di test che utilizzano $BASHPID, questa è l'altra cosa che mi mancava.
Wildcard il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.