it-swarm.com.de

Auswirkungen auf die Sicherheit, wenn vergessen wird, eine Variable in Bash / POSIX-Shells anzugeben

Wenn Sie unix.stackexchange.com schon eine Weile folgen, sollten Sie hoffentlich inzwischen wissen, dass eine Variable im Listenkontext nicht zitiert wird (wie in echo $var) in Bourne/POSIX-Shells (zsh ist die Ausnahme) hat eine ganz besondere Bedeutung und sollte nur durchgeführt werden, wenn Sie einen sehr guten Grund dafür haben.

Es wird hier ausführlich in einer Reihe von Fragen und Antworten behandelt (Beispiele: Warum verschluckt sich mein Shell-Skript an Leerzeichen oder anderen Sonderzeichen? , Wann ist ein doppeltes Anführungszeichen erforderlich? , - Erweiterung einer Shell-Variablen und Auswirkung von glob und Split darauf , Zitat gegen nicht zitierte String-Erweiterung )

Dies war seit der ersten Veröffentlichung der Bourne Shell Ende der 70er Jahre der Fall und wurde von der Korn Shell (eines von David Korns größtes Bedauern (Frage Nr. 7) ) oder bash, das hauptsächlich die Korn-Shell kopiert hat, und so wurde dies von POSIX/Unix angegeben.

Jetzt sehen wir hier immer noch eine Reihe von Antworten und gelegentlich sogar öffentlich veröffentlichten Shell-Code, in dem Variablen nicht zitiert werden. Sie hätten gedacht, die Leute hätten inzwischen gelernt.

Nach meiner Erfahrung gibt es hauptsächlich drei Arten von Personen, die ihre Variablen nicht zitieren:

  • anfänger. Diese können entschuldigt werden, da es sich zugegebenermaßen um eine völlig unintuitive Syntax handelt. Und es ist unsere Aufgabe auf dieser Website, sie zu erziehen.

  • vergessliche Menschen.

  • menschen, die auch nach wiederholtem Hämmern nicht überzeugt sind und der Meinung sind, dass der Bourne Shell-Autor sicherlich nicht beabsichtigt hat, alle unsere Variablen zu zitieren.

Vielleicht können wir sie überzeugen, wenn wir das mit solchen Verhaltensweisen verbundene Risiko aufdecken.

Was ist das Schlimmste, was passieren kann, wenn Sie vergessen, Ihre Variablen zu zitieren? Ist es wirklich so schlecht?

Von welcher Art von Verwundbarkeit sprechen wir hier?

In welchen Kontexten kann es ein Problem sein?

214

Präambel

Erstens würde ich sagen, dass dies nicht der richtige Weg ist, um das Problem anzugehen. Es ist ein bisschen so, als würde man sagen "du solltest keine Menschen ermorden, weil du sonst ins Gefängnis gehst".

Ebenso zitieren Sie Ihre Variable nicht, da Sie sonst Sicherheitslücken einführen. Sie zitieren Ihre Variablen, weil es falsch ist, dies nicht zu tun (aber wenn die Angst vor dem Gefängnis helfen kann, warum nicht).

Eine kleine Zusammenfassung für diejenigen, die gerade in den Zug gesprungen sind.

In den meisten Shells gilt das Fehlen einer variablen Erweiterung (obwohl dies (und der Rest dieser Antwort) auch für die Befehlssubstitution (`...` oder $(...)) und die arithmetische Erweiterung ($((...)) oder $[...]) gilt) eine ganz besondere Bedeutung. Der beste Weg, es zu beschreiben, ist, als würde man eine Art impliziten split + glob operator¹ aufrufen.

cmd $var

in einer anderen Sprache würde so etwas geschrieben werden wie:

cmd(glob(split($var)))

$var wird zuerst nach komplexen Regeln, die den speziellen Parameter $IFS (den Teil split) betreffen, in eine Liste von Wörtern aufgeteilt, und dann wird jedes Wort, das sich aus dieser Aufteilung ergibt, als Muster betrachtet Dies wird zu einer Liste von Dateien erweitert, die damit übereinstimmen (der glob Teil).

Wenn beispielsweise $var*.txt,/var/*.xml und $IFS, enthält, wird cmd mit einer Reihe von Argumenten aufgerufen, wobei das erste cmd und das nächste die txt -Dateien im aktuellen Verzeichnis und die xml -Dateien in /var sind.

Wenn Sie cmd nur mit den beiden Literalargumenten cmd und *.txt,/var/*.xml aufrufen möchten, schreiben Sie:

cmd "$var"

welches in Ihrer anderen vertrauten Sprache wäre:

cmd($var)

Was meinen wir mit Schwachstelle in einer Shell?

Schließlich ist seit Anbeginn der Zeit bekannt, dass Shell-Skripte nicht in sicherheitsrelevanten Kontexten verwendet werden sollten. Sicher, OK, eine Variable nicht in Anführungszeichen zu setzen, ist ein Fehler, aber das kann nicht so viel Schaden anrichten, oder?

Nun, trotz der Tatsache, dass Ihnen irgendjemand sagen würde, dass Shell-Skripte niemals für Web-CGIs verwendet werden sollten oder dass die meisten Systeme heutzutage glücklicherweise keine setuid/setgid-Shell-Skripte zulassen, eine Sache, die Shellshock (der remote ausnutzbare Bash-Fehler, der das verursacht hat) Schlagzeilen im September 2014) enthüllten, dass Shells immer noch häufig verwendet werden, wo sie wahrscheinlich nicht verwendet werden sollten: in CGIs, in DHCP-Client-Hook-Skripten, in Sudoers-Befehlen, die von (wenn nicht as) aufgerufen werden) setuid Befehle ...

Manchmal unwissentlich. Zum Beispiel ruft system('cmd $PATH_INFO') in einem php/Perl/python CGI-Skript eine Shell auf, um diese Befehlszeile zu interpretieren (ganz zu schweigen von der Tatsache, dass cmd selbst möglicherweise ein Shell-Skript ist und sein Autor dies möglicherweise nie erwartet hat von einem CGI angerufen).

Sie haben eine Sicherheitslücke, wenn es einen Weg für die Eskalation von Berechtigungen gibt, dh wenn jemand (nennen wir ihn der Angreifer) in der Lage ist, etwas zu tun, für das er nicht bestimmt ist.

Das bedeutet ausnahmslos der Angreifer Daten bereitstellen, Daten, die von einem privilegierten Benutzer/Prozess verarbeitet werden, der versehentlich etwas tut, was er nicht tun sollte, in den meisten Fällen aufgrund eines Fehlers.

Grundsätzlich haben Sie ein Problem, wenn Ihr fehlerhafter Code Daten unter der Kontrolle von dem Angreifer verarbeitet.

Nun ist es nicht immer offensichtlich, woher diese Daten kommen können, und es ist oft schwer zu sagen, ob Ihr Code jemals nicht vertrauenswürdige Daten verarbeiten kann.

In Bezug auf Variablen ist es im Fall eines CGI-Skripts ziemlich offensichtlich, dass die Daten die CGI-GET/POST-Parameter und Dinge wie Cookies, Pfad, Host ... -Parameter sind.

Bei einem Setuid-Skript (das als ein Benutzer ausgeführt wird, wenn es von einem anderen aufgerufen wird) sind dies die Argumente oder Umgebungsvariablen.

Ein weiterer sehr häufiger Vektor sind Dateinamen. Wenn Sie eine Dateiliste aus einem Verzeichnis abrufen, wurden möglicherweise Dateien von dem Angreifer eingefügt.

In dieser Hinsicht können Sie selbst bei der Eingabeaufforderung einer interaktiven Shell anfällig sein (z. B. bei der Verarbeitung von Dateien in /tmp oder ~/tmp).

Sogar ein ~/.bashrc kann anfällig sein (bash interpretiert es beispielsweise, wenn es über ssh aufgerufen wird, um ein ForcedCommand wie in git Serverbereitstellungen mit einigen Variablen auszuführen, die vom Client gesteuert werden).

Jetzt kann ein Skript nicht direkt aufgerufen werden, um nicht vertrauenswürdige Daten zu verarbeiten, sondern es kann von einem anderen Befehl aufgerufen werden, der dies tut. Oder Ihr falscher Code wird möglicherweise in Skripte kopiert, die dies tun (von Ihnen 3 Jahre später oder einem Ihrer Kollegen). Ein Ort, an dem es besonders kritisch ist, sind Antworten auf Q & A-Sites, da Sie nie wissen, wo Kopien Ihres Codes landen könnten.

Auf das Geschäft; wie schlimm ist es?

Wenn eine Variable (oder Befehlsersetzung) nicht in Anführungszeichen gesetzt wird, ist dies bei weitem die häufigste Ursache für Sicherheitslücken im Zusammenhang mit Shell-Code. Teilweise, weil diese Fehler häufig zu Sicherheitslücken führen, aber auch, weil es so häufig vorkommt, dass nicht zitierte Variablen angezeigt werden.

Wenn Sie nach Schwachstellen im Shell-Code suchen, müssen Sie zunächst nach nicht zitierten Variablen suchen. Es ist leicht zu erkennen, oft ein guter Kandidat, und im Allgemeinen leicht, von Angreifern kontrollierte Daten zurückzuverfolgen.

Es gibt unendlich viele Möglichkeiten, wie eine nicht zitierte Variable zu einer Sicherheitsanfälligkeit werden kann. Ich werde hier nur einige allgemeine Trends nennen.

Offenlegung von Informationen

Die meisten Leute werden aufgrund des Teils split ​​auf Fehler stoßen, die mit nicht zitierten Variablen verbunden sind (zum Beispiel ist es heutzutage üblich, dass Dateien Leerzeichen in ihren Namen haben und Leerzeichen den Standardwert von IFS haben). Viele Leute werden den Teil glob übersehen. Der Teil glob ist mindestens so gefährlich wie der Teil split.

Globbing bei nicht bereinigten externen Eingaben bedeutet der Angreifer kann Sie dazu bringen, den Inhalt eines beliebigen Verzeichnisses zu lesen.

Im:

echo You entered: $unsanitised_external_input

wenn $unsanitised_external_input/* enthält, bedeutet dies, dass der Angreifer den Inhalt von / sehen kann. Keine große Sache. Interessanter wird es jedoch mit /home/*, das eine Liste der Benutzernamen auf dem Computer enthält, /tmp/*, /home/*/.forward für Hinweise auf andere gefährliche Praktiken, /etc/rc*/* für aktivierte Dienste ... Sie müssen nicht einzeln benannt werden. Der Wert /* /*/* /*/*/*... listet nur das gesamte Dateisystem auf.

Denial-of-Service-Schwachstellen.

Nehmen wir den vorherigen Fall etwas zu weit und wir haben ein DoS.

Tatsächlich ist jede nicht zitierte Variable im Listenkontext mit nicht bereinigter Eingabe mindestens eine DoS-Sicherheitsanfälligkeit.

Selbst erfahrene Shell-Scripter vergessen häufig, Dinge zu zitieren wie:

#! /bin/sh -
: ${QUERYSTRING=$1}

: ist der No-Op-Befehl. Was könnte möglicherweise falsch laufen?

Das soll $1$QUERYSTRING zuweisen, wenn $QUERYSTRING nicht gesetzt war. Auf diese Weise können Sie ein CGI-Skript auch schnell über die Befehlszeile aufrufen.

Dieser $QUERYSTRING wird jedoch immer noch erweitert, und da er nicht in Anführungszeichen steht, wird der Operator split + glob aufgerufen.

Jetzt gibt es einige Globs, deren Erweiterung besonders teuer ist. Der /*/*/*/* ist schlecht genug, da er bedeutet, Verzeichnisse bis zu 4 Ebenen tiefer aufzulisten. Zusätzlich zur Festplatten- und CPU-Aktivität bedeutet dies, dass Zehntausende von Dateipfaden gespeichert werden (40 KB hier auf einer minimalen Server-VM, davon 10.000 Verzeichnisse).

Jetzt bedeutet /*/*/*/*/../../../../*/*/*/* 40k x 10k und /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/* reicht aus, um selbst die mächtigste Maschine in die Knie zu zwingen.

Probieren Sie es selbst aus (seien Sie jedoch darauf vorbereitet, dass Ihre Maschine abstürzt oder hängt):

a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'

Natürlich, wenn der Code lautet:

echo $QUERYSTRING > /some/file

Dann können Sie die Festplatte füllen.

Führen Sie einfach eine Google-Suche nach Shell cgi oder bash cgi oder ksh cgi durch, und Sie finden einige Seiten, die Ihnen das Schreiben zeigen CGIs in Muscheln. Beachten Sie, wie die Hälfte derjenigen, die Parameter verarbeiten, anfällig sind.

Sogar David Korns eigener ist anfällig (siehe Umgang mit Cookies).

bis zu Schwachstellen bei der Ausführung von willkürlichem Code

Die Ausführung von willkürlichem Code ist die schlimmste Art von Sicherheitsanfälligkeit, denn wenn der Angreifer einen Befehl ausführen kann, gibt es keine Begrenzung für seine Ausführung.

Das ist im Allgemeinen der split ​​Teil, der zu diesen führt. Diese Aufteilung führt dazu, dass mehrere Argumente an Befehle übergeben werden, wenn nur eines erwartet wird. Während die erste davon im erwarteten Kontext verwendet wird, befinden sich die anderen in einem anderen Kontext, der möglicherweise anders interpretiert wird. Besser mit einem Beispiel:

awk -v foo=$external_input '$2 == foo'

Hier sollte der Inhalt der Shell-Variablen $external_input der Variablen fooawk zugewiesen werden.

Jetzt:

$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux

Das zweite Wort, das sich aus der Aufteilung von $external_input ergibt, wird nicht foo zugewiesen, sondern als awk -Code betrachtet (hier wird ein beliebiger Befehl ausgeführt: uname).

Dies ist insbesondere bei Befehlen ein Problem, die andere Befehle ausführen können (awk, env, sed (GNU one), Perl, find...), insbesondere bei den Varianten GNU) (die Optionen nach Argumenten akzeptieren). Manchmal würden Sie nicht vermuten, dass Befehle andere wie ksh, bash oder zshs [ oder printf.. ausführen können.

for file in *; do
  [ -f $file ] || continue
  something-that-would-be-dangerous-if-$file-were-a-directory
done

Wenn wir ein Verzeichnis mit dem Namen x -o yes erstellen, wird der Test positiv, da es sich um einen völlig anderen bedingten Ausdruck handelt, den wir auswerten.

Schlimmer noch, wenn wir eine Datei mit dem Namen x -a a[0$(uname>&2)] -gt 1 erstellen, die mindestens alle ksh-Implementierungen enthält (einschließlich sh der meisten kommerziellen Unices und einiger BSDs), die uname ausführt, da diese Shells eine arithmetische Auswertung der numerischen Vergleichsoperatoren von durchführen den Befehl [.

$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux

Gleiches gilt für bash für einen Dateinamen wie x -a -v a[0$(uname>&2)].

Wenn sie keine willkürliche Hinrichtung erhalten können, kann sich der Angreifer mit weniger Schaden zufrieden geben (was zu einer willkürlichen Hinrichtung beitragen kann). Jeder Befehl, der Dateien schreiben oder Berechtigungen, Eigentümer oder Haupt- oder Nebeneffekte ändern kann, kann ausgenutzt werden.

Mit Dateinamen können alle möglichen Dinge erledigt werden.

$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done

Und am Ende machen Sie .. beschreibbar (rekursiv mit GNU chmod).

Skripte, die Dateien in öffentlich beschreibbaren Bereichen wie /tmp automatisch verarbeiten, sind sehr sorgfältig zu schreiben.

Was ist mit [ $# -gt 1 ]?

Das finde ich ärgerlich. Einige Leute machen sich die Mühe, sich zu fragen, ob eine bestimmte Erweiterung problematisch sein kann, um zu entscheiden, ob sie die Anführungszeichen weglassen können.

Es ist wie zu sagen. Hey, es sieht so aus, als ob $# nicht dem Split + Glob-Operator unterliegen kann. Bitten wir die Shell, es zu teilen + Glob. Oder Hey, lass uns falschen Code schreiben, nur weil es unwahrscheinlich ist, dass der Fehler auftritt.

Wie unwahrscheinlich ist es nun? OK, $# (oder $!, $? oder eine arithmetische Substitution) darf nur Ziffern enthalten (oder - für einige), sodass der Teil glob nicht verfügbar ist. Damit der Teil split ​​etwas tun kann, müssen $IFS nur Ziffern (oder -) enthalten.

Bei einigen Shells wird $IFS möglicherweise von der Umgebung geerbt, aber wenn die Umgebung nicht sicher ist, ist das Spiel trotzdem vorbei.

Wenn Sie nun eine Funktion schreiben wie:

my_function() {
  [ $# -eq 2 ] || return
  ...
}

Das bedeutet, dass das Verhalten Ihrer Funktion von dem Kontext abhängt, in dem sie aufgerufen wird. Mit anderen Worten, $IFS wird zu einer der Eingaben dafür. Genau genommen sollte es beim Schreiben der API-Dokumentation für Ihre Funktion ungefähr so ​​aussehen:

# my_function
#   inputs:
#     $1: source directory
#     $2: destination directory
#   $IFS: used to split $#, expected not to contain digits...

Und Code, der Ihre Funktion aufruft, muss sicherstellen, dass $IFS keine Ziffern enthält. All das, weil Sie keine Lust hatten, diese zwei doppelten Anführungszeichen einzugeben.

Damit dieser [ $# -eq 2 ]- Fehler zu einer Sicherheitslücke wird, muss der Wert von $IFS irgendwie unter die Kontrolle von dem Angreifer geraten. Es ist vorstellbar, dass dies normalerweise nicht passieren würde, wenn es dem Angreifer nicht gelungen wäre, einen anderen Fehler auszunutzen.

Das ist aber nicht ungewöhnlich. Ein häufiger Fall ist, wenn Benutzer vergessen, Daten zu bereinigen, bevor sie im arithmetischen Ausdruck verwendet werden. Wir haben oben bereits gesehen, dass es in einigen Shells die Ausführung von beliebigem Code ermöglichen kann, aber in allen erlaubt es dem Angreifer, jeder Variablen einen ganzzahligen Wert zu geben.

Zum Beispiel:

n=$(($1 + 1))
if [ $# -gt 2 ]; then
  echo >&2 "Too many arguments"
  exit 1
fi

Und mit einem $1 mit dem Wert (IFS=-1234567890) hat diese arithmetische Auswertung den Nebeneffekt, dass IFS eingestellt wird und der nächste Befehl [ fehlschlägt, was bedeutet, dass die Prüfung auf zu viele Argumente umgangen wird.

Was ist, wenn der Operator split + glob nicht aufgerufen wird?

Es gibt einen anderen Fall, in dem Anführungszeichen für Variablen und andere Erweiterungen benötigt werden: wenn sie als Muster verwendet werden.

[[ $a = $b ]]   # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac

testen Sie nicht, ob $a und $b identisch sind (außer bei zsh), sondern ob $a mit dem Muster in $b übereinstimmt. Und Sie müssen $b zitieren, wenn Sie als Zeichenfolgen vergleichen möchten (dasselbe in "${a#$b}" oder "${a%$b}" oder "${a##*$b*}", wobei $b in Anführungszeichen gesetzt werden sollte, wenn es nicht als Muster verwendet werden soll).

Dies bedeutet, dass [[ $a = $b ]] in Fällen, in denen $a sich von $b unterscheidet (z. B. wenn $aanything und $b* ist) oder false zurückgeben kann, wenn beide identisch sind (z. B. wenn beide $a und $b[a] sind).

Kann dies zu einer Sicherheitslücke führen? Ja, wie jeder Fehler. Hier kann der Angreifer den logischen Codefluss Ihres Skripts ändern und/oder die Annahmen, die Ihr Skript macht, brechen. Zum Beispiel mit einem Code wie:

if [[ $1 = $2 ]]; then
   echo >&2 '$1 and $2 cannot be the same or damage will incur'
   exit 1
fi

Der Angreifer kann die Prüfung umgehen, indem er '[a]' '[a]' übergibt.

Wenn weder dieser Mustervergleich noch der Operator split + glob zutreffen, wie groß ist dann die Gefahr, dass eine Variable nicht in Anführungszeichen gesetzt wird?

Ich muss zugeben, dass ich schreibe:

a=$b
case $a in...

Dort schadet das Zitieren nicht, ist aber nicht unbedingt notwendig.

Ein Nebeneffekt des Weglassens von Zitaten in diesen Fällen (z. B. in Fragen und Antworten) besteht jedoch darin, dass Anfänger eine falsche Nachricht erhalten können: dass es in Ordnung sein kann, Variablen nicht zu zitieren.

Zum Beispiel könnten sie denken, wenn a=$b in Ordnung ist, dann export a=$b wäre auch (was es ist nicht in vielen Shells wie es in Argumenten zum Befehl export ist, also im Listenkontext) oder env a=$b.

Was ist mit zsh?

zsh hat die meisten dieser Designprobleme behoben. Wenn Sie in zsh (zumindest wenn Sie sich nicht im sh/ksh-Emulationsmodus befinden) Teilen oder Globbing oder Mustervergleich möchten, haben Sie um es explizit anzufordern: $=var zum Teilen und $~var zum Glob oder zum Behandeln des Inhalts der Variablen als Muster.

Das Aufteilen (aber nicht das Globbing) erfolgt jedoch implizit bei nicht zitierter Befehlssubstitution (wie in echo $(cmd)).

Ein manchmal unerwünschter Nebeneffekt, wenn eine Variable nicht angegeben wird, ist Leergutentfernung. Das Verhalten von zsh ähnelt dem, was Sie in anderen Shells erreichen können, indem Sie das Globbing insgesamt (mit set -f) und das Teilen (mit IFS='') deaktivieren. Immer noch in:

cmd $var

Es wird kein split + glob geben, aber wenn $var leer ist, erhält cmd anstelle eines leeren Arguments überhaupt kein Argument.

Das kann Fehler verursachen (wie der offensichtliche [ -n $var ]). Das kann möglicherweise die Erwartungen und Annahmen eines Skripts brechen und Schwachstellen verursachen, aber ich kann gerade kein nicht allzu weit hergeholtes Beispiel finden.

Was ist, wenn Sie do den Operator split + glob benötigen?

Ja, in der Regel möchten Sie Ihre Variable nicht in Anführungszeichen setzen. Aber dann müssen Sie sicherstellen, dass Sie Ihre Operatoren split ​​und glob korrekt einstellen, bevor Sie sie verwenden. Wenn Sie nur den Teil split ​​und nicht den Teil glob möchten (was meistens der Fall ist), müssen Sie globbing (set -o noglob/set -f) und deaktivieren Fix $IFS. Andernfalls verursachen Sie ebenfalls Sicherheitslücken (wie das oben erwähnte CGI-Beispiel von David Korn).

Fazit

Kurz gesagt, es kann in der Tat sehr gefährlich sein, eine Variable (oder eine Befehlssubstitution oder eine arithmetische Erweiterung) in Shells nicht in Anführungszeichen zu setzen, insbesondere wenn sie in falschen Kontexten ausgeführt wird, und es ist sehr schwer zu wissen, welche diese falschen Kontexte sind.

Das ist einer der Gründe, warum es als schlechte Praxis angesehen wird.

Danke fürs Lesen. Wenn es über deinen Kopf geht, mach dir keine Sorgen. Man kann nicht erwarten, dass jeder alle Auswirkungen des Schreibens seines Codes so versteht, wie er ihn schreibt. Aus diesem Grund haben wir Empfehlungen für bewährte Verfahren, damit sie befolgt werden können, ohne unbedingt zu verstehen, warum.

(und falls dies noch nicht offensichtlich ist, vermeiden Sie es bitte, sicherheitsrelevanten Code in Shells zu schreiben).

Und bitte zitieren Sie Ihre Variablen in Ihren Antworten auf dieser Seite!


¹In ksh93 und pdksh und Derivaten wird Klammererweiterung auch ausgeführt, sofern das Globbing nicht deaktiviert ist (bei ksh93 -Versionen bis ksh93u + auch dann, wenn die Option braceexpand deaktiviert ist).

207

[Inspiriert von diese Antwort von cas .]

Aber was wenn …?

Was aber, wenn mein Skript eine Variable auf einen bekannten Wert setzt, bevor sie verwendet wird? Was ist insbesondere, wenn eine Variable auf einen von zwei oder mehr möglichen Werten gesetzt wird (aber immer sie auf etwas Bekanntes setzt) ​​und keiner der Werte Leerzeichen oder Glob-Zeichen enthält? Ist es nicht sicher, es ohne Anführungszeichen zu verwenden in diesem Fall?

Und was ist, wenn einer der möglichen Werte die leere Zeichenfolge ist und ich von "Leergutentfernung" abhängig bin? Das heißt, wenn die Variable die leere Zeichenfolge enthält, möchte ich die leere Zeichenfolge nicht in meinem Befehl erhalten. Ich will nichts bekommen. Zum Beispiel,

wenn some_condition
 Dann 
 Ignorierfall = "- i" 
 Sonst 
 Ignorierkoffer = "" 
 Fi 
 # Beachten Sie, dass die Anführungszeichen in den obigen Befehlen lauten nicht dringend benötigt.
 grep $ ignorecase andere_grep_args

Ich kann nicht sagen grep "$ignorecase" other_grep_args; Das schlägt fehl, wenn $ignorecase die leere Zeichenfolge ist.

Antwort:

Wie in der anderen Antwort erläutert, schlägt dies immer noch fehl, wenn IFS einen - Oder einen i enthält. Wenn Sie sichergestellt haben, dass IFS kein Zeichen in Ihrer Variablen enthält (und Sie sicher sind, dass Ihre Variable keine Glob-Zeichen enthält), ist dies wahrscheinlich sicher.

Es gibt jedoch einen Weg, der sicherer ist (obwohl er etwas hässlich und nicht intuitiv ist): Verwenden Sie ${ignorecase:+"$ignorecase"}. Von der POSIX Shell Command Language-Spezifikation , unter 2.6.2 Parametererweiterung ,

${parameter:+[Word]}

    Verwenden Sie einen alternativen Wert. Wenn parameter nicht gesetzt oder null ist, wird null ersetzt. Andernfalls wird die Erweiterung von Word (oder eine leere Zeichenfolge, wenn Word weggelassen wird) ersetzt.

Der Trick hier ist, wie es ist, dass wir ignorecase als parameter und "$ignorecase" Als verwenden Word. Also bedeutet ${ignorecase:+"$ignorecase"}

Wenn $ignorecase Nicht gesetzt oder null (d. H. Leer) ist, wird null (d. H. Nicht zitiert nichts) ersetzt; Andernfalls wird die Erweiterung von "$ignorecase" ersetzt.

Dies bringt uns dahin, wo wir hin wollen: Wenn die Variable auf die leere Zeichenfolge gesetzt ist, wird sie "entfernt" (dieser gesamte, verschlungene Ausdruck ergibt nichts - nicht einmal eine leere Zeichenfolge ), und wenn die Variable einen nicht leeren Wert hat, erhalten wir diesen Wert in Anführungszeichen.


Aber was wenn …?

Aber was ist, wenn ich eine Variable habe, die in Wörter aufgeteilt werden soll/muss? (Dies ist ansonsten wie im ersten Fall. Mein Skript hat die Variable festgelegt, und ich bin sicher, dass sie keine Glob-Zeichen enthält. Es kann jedoch Leerzeichen enthalten, und ich möchte, dass sie im Leerzeichen in separate Argumente aufgeteilt werden Grenzen.
P.S. Ich möchte immer noch Leergut entfernen.)

Zum Beispiel,

wenn some_condition
 dann 
 Kriterien = "- Typ f" 
 Sonst 
 Kriterien = "" 
 Fi 
 Wenn some_other_condition
 dann 
 Kriterien = "$ Kriterien -mtime +42" 
 fi 
 finden "$ start_directory" $ Kriterien andere_finden_args

Antwort:

Sie könnten denken, dass dies ein Fall für die Verwendung von eval ist. Nein! Widerstehen Sie der Versuchung, hier überhaupt über die Verwendung von eval nachzudenken.

Wenn Sie sichergestellt haben, dass IFS kein Zeichen in Ihrer Variablen enthält (mit Ausnahme der Leerzeichen, die Sie berücksichtigen möchten), und Sie sicher sind, dass Ihre Variable keine Glob-Zeichen enthält , dann ist das oben genannte wahrscheinlich sicher.

Wenn Sie jedoch bash (oder ksh, zsh oder yash) verwenden, gibt es einen sichereren Weg: Verwenden Sie ein Array:

wenn some_condition
 dann 
 Kriterien = (- Typ f) # Man könnte sagen "Kriterien = (" - Typ "" f ")", aber es ist wirklich unnötig. 
 Sonst 
 Kriterien = () # Unterlassen Sie Verwenden Sie Anführungszeichen für diesen Befehl! 
 fi 
 if some_other_condition
 dann 
 Kriterien + = (- mtime +42) # Hinweis: nicht `=`, sondern `+= `, um einem Array etwas hinzuzufügen (anzuhängen). 
 fi 
 find" $ start_directory "" $ {Kriterien [@]} " andere_finden_args

Von bash (1) ,

Jedes Element eines Arrays kann mit ${name[subscript]} Referenziert werden. … Wenn subscript@ oder *, das Wort wird auf alle Mitglieder von name erweitert. Diese Indizes unterscheiden sich nur, wenn das Wort in doppelten Anführungszeichen steht. Wenn das Wort in doppelte Anführungszeichen gesetzt ist, erweitert… ${name[@]} Jedes Element von name Zu einem separaten Wort.

"${criteria[@]}" Wird also (im obigen Beispiel) auf die null, zwei oder vier Elemente des criteria -Arrays erweitert, die jeweils in Anführungszeichen stehen. Insbesondere wenn keine der Bedingungen s wahr ist, hat das Array criteria keinen Inhalt (wie durch die Anweisung criteria=() festgelegt) und "${criteria[@]}" Wird zu nichts ausgewertet (nicht einmal eine unbequeme leere Zeichenfolge).


Dies wird besonders interessant und kompliziert, wenn Sie mit mehreren Wörtern arbeiten, von denen einige dynamische (Benutzer-) Eingaben sind, die Sie nicht im Voraus kennen und die möglicherweise Leerzeichen oder andere Sonderzeichen enthalten. Erwägen:

printf "Geben Sie den zu suchenden Dateinamen ein:" 
 Lesen Sie fname 
, wenn ["$ fname"! = ""] 
, dann 
 Kriterien + = (- Name " $ fname ") 
 fi

Beachten Sie, dass $fname Mit jeweils angegeben wird, wenn es verwendet wird. Dies funktioniert auch dann, wenn der Benutzer etwas wie foo bar Oder foo* Eingebt. "${criteria[@]}" Wird zu -name "foo bar" Oder -name "foo*" Ausgewertet. (Denken Sie daran, dass jedes Element des Arrays in Anführungszeichen steht.)

Arrays funktionieren nicht in allen POSIX-Shells. Arrays sind ein ksh/bash/zsh/yash-ismus. Außer… es gibt eins Array, das alle Shells unterstützen: die Argumentliste a.k.a. "[email protected]". Wenn Sie mit der Argumentliste fertig sind, mit der Sie aufgerufen wurden (z. B. Sie haben alle "Positionsparameter" (Argumente) in Variablen kopiert oder auf andere Weise verarbeitet), können Sie die Argumentliste als Array verwenden:

wenn some_condition
 Dann 
 Set - -type f # Man könnte sagen `set -" -type "" f "`, aber es ist wirklich unnötig. 
 Sonst 
 set - 
 fi 
 if some_other_condition
 Dann 
 Setze - "$ @" -mtime +42 
 Fi 
 # Ähnlich: setze - "$ @" -name "$ fname" 
 find "$ start_directory" "$ @" andere_finden_args

Das Konstrukt "[email protected]" (Das historisch gesehen an erster Stelle stand) hat dieselbe Semantik wie "${name[@]}" - es erweitert jedes Argument (dh jedes Element der Argumentliste) zu einem separaten Wort, als ob Sie hatte "$1" "$2" "$3" … eingegeben.

Auszug aus der POSIX Shell Command Language-Spezifikation , unter 2.5.2 Spezielle Parameter ,

@

    Erweitert sich auf die Positionsparameter, beginnend mit einem, und erstellt zunächst ein Feld für jeden eingestellten Positionsparameter. … Werden die Anfangsfelder als separate Felder beibehalten,…. Wenn keine Positionsparameter vorhanden sind, erzeugt die Erweiterung von @ Nullfelder, auch wenn @ steht in doppelten Anführungszeichen; …

Der vollständige Text ist etwas kryptisch; Der entscheidende Punkt ist, dass angegeben wird, dass "[email protected]" Nullfelder erzeugen soll, wenn keine Positionsparameter vorhanden sind. Historischer Hinweis: Als "[email protected]" 1979 erstmals in der Bourne Shell (Vorgänger von bash) eingeführt wurde, gab es den Fehler, dass "[email protected]" Durch eine einzelne leere Zeichenfolge ersetzt wurde, wenn keine Positionsparameter vorhanden waren. siehe Was bedeutet ${1+"[email protected]"} in einem Shell-Skript und wie unterscheidet es sich von "[email protected]"? , Die traditionelle Bourne-Shell-Familie , - Was bedeutet ${1+"[email protected]"} ... und wo ist es notwendig? und "[email protected]" Gegenüber ${1+"[email protected]"} .


Arrays helfen auch in der ersten Situation:

wenn some_condition
 dann 
 ignorecase = (- i) # Man könnte sagen "ignorecase = (" - i ")", aber es ist wirklich unnötig. 
 Sonst 
 ignorecase = () # Unterlassen Sie Verwenden Sie für diesen Befehl Anführungszeichen! 
 fi 
 grep "$ {ignorecase [@]}" andere_grep_args

____________________

P.S. (csh)

Dies sollte selbstverständlich sein, aber zum Nutzen der Leute, die hier neu sind: csh, tcsh usw. sind keine Bourne/POSIX-Shells. Sie sind eine ganz andere Familie. Ein Pferd einer anderen Farbe. Ein ganz anderes Ballspiel. Eine andere Katzenrasse. Vögel einer anderen Feder. Und vor allem eine andere Dose Würmer.

Einige der auf dieser Seite genannten Informationen gelten für csh. Beispiel: Es ist eine gute Idee, alle Ihre Variablen zu zitieren, es sei denn, Sie haben einen guten Grund, dies nicht zu tun, und Sie sind sicher, dass Sie wissen, was Sie tun. In csh ist jede Variable ein Array - es kommt nur so vor, dass fast jede Variable ein Array mit nur einem Element ist und sich ähnlich wie eine gewöhnliche Shell-Variable in Bourne/POSIX-Shells verhält. Und die Syntax ist schrecklich anders (und ich meine schrecklich). Wir werden hier also nichts mehr über Muscheln der csh-Familie sagen.

Ich war skeptisch gegenüber Stéphanes Antwort, aber es ist möglich, $#:

$ set `seq 101`

$ IFS=0

$ echo $#
1 1

oder $?:

$ IFS=0

$ awk 'BEGIN {exit 101}'

$ echo $?
1 1

Dies sind erfundene Beispiele, aber das Potenzial besteht.

11
Steven Penny