Bash-Skript: Unterschiedliche Ergebnisse beim Aufruf mit oder ohne Sudo

10

In Ubuntu 16.04.3 habe ich ein sehr einfaches Bash-Skript:

test.sh

[[ 0 == 0 ]] && result="true" || result="false"
echo $result
echo $USER $SHELL $0

Wenn ich es als Nicht-Root-Benutzer meoder als nenne root, funktioniert es wie erwartet. Wenn ich benutze sudo ./test.sh, beschwert es sich über einen Syntaxfehler:

$ ./test.sh
true
me /bin/bash ./test.sh

$ sudo su
# ./test.sh 
true
root /bin/bash ./test.sh

# exit
$ sudo ./test.sh
./test.sh: 1: ./test.sh: [[: not found
false
root /bin/bash ./test.sh

Was könnte das verursachen? Wie kann ich das Problem beheben, damit medieses Skript sowohl normal als auch mit verwendet werden kann sudo?

James Newton
quelle
3
Profi-Tipp: Es macht keinen Sinn zu laufensudo su . Einfach laufen sudo -ioder sudo -sstattdessen.
Terdon
@terdon sudo -iändert den Speicherort in /root. sudo suoder sudo -sändern Sie nicht den Speicherort des Verzeichnisses.
James Newton
Ja, lesen Sie die Frage, auf die ich zuvor verlinkt habe, warum. Und sorry, ich habe meinen vorherigen Kommentar bearbeitet, den ich vergessen hatte zu erwähnen -s.
Terdon

Antworten:

20

Jedes Skript beginnt mit einem Shebang , ohne ihn weiß die Shell, die Ihr Skript startet, nicht, welcher Interpreter Ihr Skript 1 ausführen soll, und kann es - wie im Fall von sudo ./script.shhier - mit ausführen, mit shdem in Ubuntu 16.04 verknüpft ist dash. Der bedingte Ausdruck [[ ist ein bashzusammengesetzter Befehl , dashweiß also nicht, wie er damit umgehen soll, und löst den aufgetretenen Fehler aus.

Die Lösung hier ist hinzuzufügen

#!/bin/bash

als erste Zeile Ihres Skripts. Sie erhalten möglicherweise das gleiche Ergebnis, wenn Sie es explizit mit aufrufen sudo bash ./script.sh, aber ein Shebang ist der richtige Weg.
Fügen Sie hinzu, welche Shell Ihr Skript ausführt echo $0. Das ist nicht dasselbe wie echo $SHELL unter Berufung auf wiki.archlinux.org :

SHELL enthält den Pfad zur bevorzugten Shell des Benutzers. Beachten Sie, dass dies nicht unbedingt die aktuell ausgeführte Shell ist, obwohl Bash diese Variable beim Start festlegt.

1: Wie Sie begann ./test.shmit bashihm nur angenommen bash, das gleiche gilt für den sudo suSub - Shell.

Dessert
quelle
1
Beachten Sie auch, dass bash ein Skript ohne shebang mit bash ausführt, nicht /bin/sh.
Muru
@ Dessert Das behebt es. Vielen Dank! Wie kann ich in einem Skript überprüfen, auf welcher Shell es ausgeführt wird? ( echo $0Gibt mir den Namen des Skripts: ./test.sh)
James Newton
@ JamesNewton kein tragbarer Weg, AFAIK, aber Sie können überprüfen, auf welche /proc/$$/exePunkte. Sie können auch verschiedene Variablen wie testen $BASH_VERSION, $ZSH_VERSIONusw. (aber Strich stellt keine solche Variable)
muru
5

Wie @dessert erklärte , besteht das Problem hier darin, dass Ihr Skript keine Shebang-Zeile enthält . Ohne Shebang sudowird standardmäßig versucht, die Datei mit auszuführen /bin/sh. Ich konnte es nirgendwo dokumentiert finden, aber ich bestätigte es, indem ich den sudoQuellcode überprüfte, in dem ich Folgendes in der Datei fand pathnames.h:

#ifndef _PATH_BSHELL
#define _PATH_BSHELL "/bin/sh"
#endif /* _PATH_BSHELL */

Dies bedeutet "setzen, wenn die Variable _PATH_BSHELLnicht definiert ist, setzen Sie sie auf /bin/sh". Dann configurehaben wir in dem Skript, das im Quell-Tarball enthalten ist:

for p in "/bin/bash" "/usr/bin/sh" "/sbin/sh" "/usr/sbin/sh" "/bin/ksh" "/usr/bin/ksh" "/bin/bash" "/usr/bin/bash"; do
    if test -f "$p"; then
    found=yes
    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $p" >&5
$as_echo "$p" >&6; }
    cat >>confdefs.h <<EOF
#define _PATH_BSHELL "$p"
EOF

    break
    fi
done

Diese Schleife wird sucht /bin/bash, /usr/bin/sh, /sbin/sh, /usr/sbin/shoder /bin/kshund dann setzt die _PATH_BSHELLzu je nachdem was zuerst gefunden wurde . Da /bin/shwar der erste in der Liste und es existiert, _PATH_BSHELList auf gesetzt /bin/sh. Das Ergebnis all dessen ist, dass die Standard-Shell von, sudosofern nicht anders definiert, lautet /bin/sh.

Also, sudowird standardmäßig die Dinge zum Laufen mit /bin/shund auf Ubuntu, das eine symbolische Verknüpfung zu ist dash, ein minimal POSIX - konform Shell:

$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Feb 27  2015 /bin/sh -> dash

Das [[Konstrukt ist eine Bash-Funktion, es ist nicht durch den POSIX-Standard definiert und wird nicht verstanden von dash:

$ bash -c '[[ true ]] && echo yes'
yes
$ dash -c '[[ true ]] && echo yes'
dash: 1: [[: not found

Im Detail in den drei Aufrufen, die Sie versucht haben:

  1. ./test.sh

    Nein sudo; Wenn keine Shebang-Zeile vorhanden ist, versucht Ihre Shell, die Datei selbst auszuführen. Da Sie ausgeführt werden, bashwird dies effektiv ausgeführt bash ./test.shund funktioniert.

  2. sudo sugefolgt von ./test.sh.

    Hier starten Sie eine neue Shell für den Benutzer root. Dies ist die Shell, die in der $SHELLUmgebungsvariablen für diesen Benutzer definiert ist. Unter Ubuntu lautet die Standard-Shell von root bash:

    $ grep root /etc/passwd
    root:x:0:0:root:/root:/bin/bash
  3. sudo ./test.sh

    Hier können Sie sudoden Befehl direkt ausführen. Da die Standard-Shell /bin/shwie oben erläutert ist, führt dies dazu, dass das Skript ausgeführt wird. Dies /bin/shist der Fall dashund schlägt fehl, da dashdies nicht verstanden wird [[.


Hinweis : Die Details zum sudoFestlegen der Standard-Shell scheinen etwas komplexer zu sein. Ich habe versucht, die in meiner Antwort genannten Dateien so zu ändern, dass sie auf zeigen, war /bin/bashaber sudoimmer noch standardmäßig auf /bin/sh. Es muss also einige andere Stellen im Quellcode geben, an denen die Standard-Shell definiert ist. Trotzdem bleibt der Hauptpunkt (der sudostandardmäßig verwendet wird sh) bestehen.

Terdon
quelle
Ich konnte es nirgendwo dokumentiert finden - ich auch nicht, ich nahm nur an, dass es /bin/shaus der Fehlermeldung verwendet werden würde - was könnte es sonst noch möglich sein? Die Frage wird wunderbar beantwortet in Welche Shell verwendet sudo · SO , siehe auch man sudoAbschnitt BEFEHLSAUSFÜHRUNG . Es stellt sich heraus, sudodass keine Zwischenschale verwendet wird !
Dessert
1
@dessert ja, es verwendet eine eigene Implementierung des execveSystemaufrufs, der standardmäßig verwendet wird sh. Und nein, Zwischen-Shells sind irrelevant. Hier geht es nicht um die Shell, die den Befehl ausführt, sondern um den Shell-Interpreter, der zum Lesen des angegebenen Shell-Skripts verwendet wird. Nein, es wird keine Zwischen-Shell gestartet, aber es wird weiterhin ein Shell-Interpreter für Shell-Skripte benötigt.
Terdon