Bash, wenn nicht mehrere Bedingungen ohne Subshell?

23

Ich möchte mehrere Bedingungen in einer Shell-if-Anweisung kombinieren und die Kombination negieren. Ich habe den folgenden Arbeitscode für eine einfache Kombination von Bedingungen:

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

Das funktioniert gut. Wenn ich es negieren möchte, kann ich den folgenden Arbeitscode verwenden:

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

Das funktioniert auch wie erwartet. Mir fällt jedoch auf, dass ich nicht weiß, was die Klammern dort tatsächlich tun. Ich möchte sie nur zum Gruppieren verwenden, aber gibt es tatsächlich eine Unterschale? (Woran erkenne ich das?) Wenn ja, gibt es eine Möglichkeit, die Bedingungen zu gruppieren, ohne eine Unterschale zu erzeugen?

Platzhalter
quelle
Ich nehme an, ich könnte auch die Boolesche Logik verwenden, um den entsprechenden Ausdruck zu bestimmen. if ! [ -f file1 ] || ! [ -f file 2 ] || ! [ -f file3 ] ; thenAber ich hätte gerne eine allgemeinere Antwort.
Wildcard
Sie können auch innerhalb der geschweiften Klammern negieren, z. B.if [[ ! -f file1 ]] && [[ ! -f file2 ]]; then
DopeGhoti
1
Ja, ich habe es gerade getestet if [ ! 1 -eq 2 ] && [ ! 2 -eq 3 ]; then echo yep; fiund es funktioniert. Ich schreibe nur aus Gewohnheit immer Tests mit doppelten Klammern. Außerdem, um sicherzustellen, dass es nicht ist bash, habe ich weiter getestet if /bin/test ! 1 -eq 2 && /bin/test ! 2 -eq 3 ; then echo yep; fiund es funktioniert auch so.
DopeGhoti
1
@Wildcard - löscht [ ! -e file/. ] && [ -r file ]Verzeichnisse. negiere es wie du willst. Natürlich ist es das, was -dtut.
mikeserv
1
Verwandte Frage (ähnlich, aber nicht so viel Diskussion und Antworten nicht so hilfreich): unix.stackexchange.com/q/156885/135943
Wildcard

Antworten:

32

Sie müssen { list;}anstelle von verwenden (list):

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

Beide sind Gruppierungsbefehle , aber{ list;} Befehle in der aktuellen Shell-Umgebung aus.

Beachten Sie, dass das ;in { list;}benötigt wird, um die Liste vom }umgekehrten Wort abzugrenzen. Sie können auch ein anderes Trennzeichen verwenden. Das Leerzeichen (oder ein anderes Trennzeichen) nach {ist ebenfalls erforderlich.

cuonglm
quelle
Vielen Dank! Was mich anfangs gestolpert hat (da ich awköfter geschweifte Klammern verwende als in bash), ist die Notwendigkeit eines Leerzeichens nach der offenen geschweiften Klammer. Sie erwähnen "Sie können auch andere Trennzeichen verwenden"; irgendwelche Beispiele?
Wildcard
@Wildcard: Ja, das Leerzeichen nach der offenen geschweiften Klammer wird benötigt. Ein Beispiel für ein anderes Trennzeichen ist ein Zeilenumbruch, da Sie häufig eine Funktion definieren möchten.
Donnerstag,
@don_crissti: In zshkönnen Sie Whitespace verwenden
cuonglm
@don_crissti: das &ist auch ein separator, den du benutzen kannst { echo 1;:&}, mehrere newline können auch benutzt werden.
3.
2
Sie können jedes Metazeichen-Zitat aus manual they must be separated from list by whitespace or another shell metacharacter.In bash verwenden metacharacter: | & ; ( ) < > space tab . Für diese spezielle Aufgabe glaube ich, dass jeder von & ; ) space tabihnen funktionieren wird. .... .... Von zsh (als Kommentar) each sublist is terminated by &', &!', or a newline.
8

Sie können die testFunktionalität vollständig nutzen , um das zu erreichen, was Sie wollen. Aus der Manpage von 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.

Ihr Zustand könnte also so aussehen:

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

Verwenden Sie zum Negieren ausgeblendete Klammern:

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

Um eine komplexe Bedingung in der Shell portabel zu negieren, müssen Sie entweder De Morgans Gesetz anwenden und die Negation in den [Aufrufen ganz nach unten schieben ...

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

... oder du musst then :; else...

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

if ! commandist nicht tragbar und ist es auch nicht [[.

Wenn Sie keine vollständige Portabilität benötigen, schreiben Sie kein Shell-Skript . Sie sind tatsächlich mehr wahrscheinlich zu finden /usr/bin/perlauf einem zufällig ausgewählten Unix , als Sie sind bash.

zwol
quelle
1
!ist POSIX, das heutzutage tragbar genug ist. Sogar Systeme, die noch mit der Bourne-Shell ausgeliefert werden, haben irgendwo anders im Dateisystem einen POSIX-Sh, mit dem Sie Ihre Standardsyntax interpretieren können.
Stéphane Chazelas
@ StéphaneChazelas Das ist technisch gesehen richtig, aber meiner Erfahrung nach ist es einfacher, ein Skript in Perl umzuschreiben, als die mühsame Suche und Aktivierung der POSIX-kompatiblen Shell-Umgebung ab /bin/sh. Autoconf-Skripte machen das, was Sie vorschlagen, aber ich denke, wir alle wissen, wie wenig eine Bestätigung das ist.
zwol
5

Andere haben die Gruppierung der {zusammengesetzten Befehle zur Kenntnis genommen. ;}Wenn Sie jedoch identische Tests an einem Satz durchführen , möchten Sie möglicherweise eine andere Art verwenden:

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

... wie an anderer Stelle gezeigt wird mit { :;} , gibt es keine Schwierigkeiten beim Verschachteln von zusammengesetzten Befehlen ...

Beachten Sie, dass die oben genannten Tests (normalerweise) für reguläre Dateien. Wenn Sie nur nach vorhandenen , lesbaren Dateien suchen , die keine Verzeichnisse sind:

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

Wenn es Ihnen egal ist, ob es sich um Verzeichnisse handelt oder nicht:

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

... funktioniert für jede lesbare , zugängliche Datei, hängt aber wahrscheinlich für Fifos ohne Autoren.

mikeserv
quelle
2

Dies ist keine vollständige Antwort auf Ihre Hauptfrage, aber ich habe festgestellt, dass Sie in einem Kommentar Verbindungstests (lesbare Datei) erwähnen. z.B,

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

Sie können dies ein wenig festigen, indem Sie eine Shell-Funktion definieren. z.B,

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

Fügen Sie [ $# = 1 ]nach Belieben eine Fehlerbehandlung hinzu (z. B. ). Die erste ifobige Aussage kann nun auf kondensiert werden

if readable_file file1  &&  readable_file file2  &&  readable_file file3

und Sie können dies noch weiter verkürzen, indem Sie den Namen der Funktion verkürzen. Ebenso können Sie die Negation definieren not_readable_file()(oder nrfkurz) und in die Funktion aufnehmen.

G-Man sagt, "Monica wiedereinsetzen"
quelle
Schön, aber warum nicht einfach eine Schleife hinzufügen? readable_files() { [ $# -eq 0 ] && return 1 ; for file in "$@" ; do [ -f "$file" ] && [ -r "$file" ] || return 1 ; done ; } (Haftungsausschluss: Ich habe diesen Code getestet und er funktioniert, aber es war kein Einzeiler. Ich habe Semikolons zum Posten hier hinzugefügt, aber ihre Platzierung nicht getestet.)
Wildcard
1
Guter Punkt. Ich möchte die geringfügige Besorgnis äußern, dass sie mehr von der Steuerungs- (Konjunktions-) Logik in der Funktion verbirgt; Jemand, der das Skript liest und sieht, if readable_files file1 file2 file3würde nicht wissen, ob die Funktion AND oder OR ausführt - obwohl (a) ich denke, es ist ziemlich intuitiv, (b) wenn Sie das Skript nicht verteilen, ist es gut genug, wenn Sie es verstehen, und (c) da ich vorgeschlagen habe, den Funktionsnamen in Unklarheit abzukürzen, bin ich nicht in der Lage, über Lesbarkeit zu sprechen. … (Fortsetzung)
G-Man sagt, dass Monica
1
(Fortsetzung) ... BTW, ist die Funktion in Ordnung so , wie Sie es geschrieben, aber Sie können ersetzen for file in "$@" ; domit for file do- es standardmäßig in "$@", und (etwas Gegen intuitiv) das ;ist nicht nur unnötig , sondern eigentlich abgeraten.
G-Man sagt, dass Monica
0

Sie können auch innerhalb der Klammertests negieren, um Ihren ursprünglichen Code wiederzuverwenden:

if [[ ! -f file1 ]] || [[ ! -f file2 ]] || [[ ! -f file3 ]] ; then
  # do stuff with the files
fi
DopeGhoti
quelle
2
Sie müssen ||statt&&
cuonglm
Der Test lautet nicht " Sind Dateien nicht vorhanden?", Sondern " Sind Dateien nicht vorhanden ?". Mit ||würde das Prädikat ausgeführt, wenn eine der Dateien nicht vorhanden ist, nicht wenn und nur wenn nicht alle Dateien vorhanden sind. NICHT (A UND B UND C) ist dasselbe wie (NICHT A) UND (NICHT B) UND (NICHT C); siehe die ursprüngliche Frage.
DopeGhoti
@ DopeGhoti, Cuonglm ist richtig. Wenn dies nicht der Fall ist (alle Dateien dort), müssen Sie es ||stattdessen aufteilen &&. Siehe meinen ersten Kommentar unter meiner Frage.
Wildcard
2
@ DopeGhoti: not (a and b)ist das gleiche wie (not a) or (not b). Siehe en.wikipedia.org/wiki/De_Morgan%27s_laws
cuonglm
1
Es muss Donnerstag sein. Donnerstags habe ich nie den Dreh raus. Korrigiert, und ich werde meinen Fuß in den Mund für die Nachwelt verlassen.
DopeGhoti
-1

Ich möchte sie nur zum Gruppieren verwenden, aber gibt es tatsächlich eine Unterschale? (Wie kann ich sagen?)

Ja, die Befehle in der (...)werden in einer Subshell ausgeführt.

Um zu testen, ob Sie sich in einer Subshell befinden, können Sie die PID Ihrer aktuellen Shell mit der PID der interaktiven Shell vergleichen.

echo $BASHPID; (echo $BASHPID); 

Beispielausgabe, die zeigt, dass Klammern eine Subshell und geschweifte Klammern nicht erzeugen:

$ echo $BASHPID; (echo $BASHPID); { echo $BASHPID;}
50827
50843
50827
$ 
David King
quelle
6
()Spawn Subshell {}...
Vielleicht
@heemayl, das ist der andere Teil meiner Frage - gibt es eine Möglichkeit, die Tatsache, dass eine Subshell erzeugt wird , auf meinem Bildschirm zu sehen oder zu demonstrieren ? (Das könnte wirklich eine separate Frage sein, wäre aber hier gut zu wissen.)
Wildcard
1
@heemayl Du hast recht! Ich hatte echo $$ && ( echo $$ )versucht, die PIDs zu vergleichen und sie waren die gleichen, aber kurz bevor ich den Kommentar abgab, der Ihnen mitteilte, dass Sie sich geirrt hatten, habe ich eine andere Sache versucht. echo $BASHPID && ( echo $BASHPID )gibt verschiedene PID's
David King
1
@heemayl echo $BASHPID && { echo $BASHPID; }gib die gleiche PID an, wie du es vorgeschlagen hast. Vielen Dank für die Ausbildung
David King
@DavidKing, danke für die Testbefehle mit $BASHPID, das ist das andere, was mir gefehlt hat.
Wildcard