Wie kann ich eine Tilde ~ als Teil einer Variablen erweitern?

12

Wenn ich eine Bash-Eingabeaufforderung öffne und Folgendes eingebe:

$ set -o xtrace
$ x='~/someDirectory'
+ x='~/someDirectory'
$ echo $x
+ echo '~/someDirectory'
~/someDirectory

Ich hatte gehofft, dass die fünfte Zeile oben gegangen wäre + echo /home/myUsername/someDirectory. Gibt es eine Möglichkeit, dies zu tun? In meinem ursprünglichen Bash-Skript wird die Variable x tatsächlich aus Daten aus einer Eingabedatei über eine Schleife wie die folgende gefüllt:

while IFS= read line
do
    params=($line)
    echo ${params[0]}
done <"./someInputFile.txt"

Trotzdem bekomme ich ein ähnliches Ergebnis mit dem echo '~/someDirectory'statt echo /home/myUsername/someDirectory.

Andrew
quelle
In ZSH ist dies x='~'; print -l ${x} ${~x}. Ich gab auf, nachdem ich basheine Weile im Handbuch durchgeblättert hatte .
Thrig
@thrig: Dies ist kein Bashismus, dieses Verhalten ist POSIX.
WhiteWinterWolf
Extrem eng verwandt (wenn nicht ein Betrüger): unix.stackexchange.com/questions/151850/…
Kusalananda
@Kusalananda: Ich bin mir nicht sicher, ob dies ein Betrug ist, da der Grund hier etwas anders ist: Das OP hat $ x nicht zwischen Anführungszeichen eingeschlossen, als es wiedergegeben wurde.
WhiteWinterWolf
Sie fügen keine Tildes in die Eingabedatei ein. Problem gelöst.
Chepner

Antworten:

11

Der POSIX-Standard schreibt eine Worterweiterung in der folgenden Reihenfolge vor (Hervorhebung ist meine):

  1. Die Tilde-Erweiterung (siehe Tilde-Erweiterung), die Parametererweiterung (siehe Parametererweiterung), die Befehlssubstitution (siehe Befehlssubstitution) und die arithmetische Erweiterung (siehe Arithmetische Erweiterung) müssen von Anfang bis Ende durchgeführt werden. Siehe Punkt 5 in Tokenerkennung.

  2. Die Feldaufteilung (siehe Feldaufteilung) muss für die in Schritt 1 erzeugten Teile der Felder durchgeführt werden, es sei denn, IFS ist null.

  3. Die Pfadnamenerweiterung (siehe Pfadnamenerweiterung) muss durchgeführt werden, es sei denn, set -f ist wirksam.

  4. Das Entfernen von Angeboten (siehe Entfernen von Angeboten) muss immer zuletzt durchgeführt werden.

Der einzige Punkt, der uns hier interessiert, ist der erste: Wie Sie sehen, wird die Tilde-Erweiterung vor der Parametererweiterung verarbeitet:

  1. Die Shell versucht eine Tilde-Erweiterung echo $x, es ist keine Tilde zu finden, also geht es weiter.
  2. Die Shell versucht eine Parametererweiterung auf echo $x, $xwird gefunden und erweitert und die Kommandozeile wird echo ~/someDirectory.
  3. Die Verarbeitung wird fortgesetzt, da die Tilde-Erweiterung bereits verarbeitet wurde ~, bleibt das Zeichen unverändert.

Durch die Verwendung der Anführungszeichen beim Zuweisen der $xhaben Sie ausdrücklich darum gebeten, die Tilde nicht zu erweitern und wie ein normales Zeichen zu behandeln. Eine Sache, die oft übersehen wird, ist, dass Sie in Shell-Befehlen nicht die gesamte Zeichenfolge in Anführungszeichen setzen müssen, damit Sie die Erweiterung direkt während der Variablenzuweisung durchführen können:

user@host:~$ set -o xtrace
user@host:~$ x=~/'someDirectory'
+ x=/home/user/someDirectory
user@host:~$ echo $x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$

Sie können die Erweiterung auch in der echoBefehlszeile ausführen, solange dies vor der Parametererweiterung möglich ist:

user@host:~$ x='someDirectory'
+ x=someDirectory
user@host:~$ echo ~/$x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$

Wenn Sie aus irgendeinem Grund die Tilde für die $xVariable ohne Erweiterung wirklich beeinflussen müssen und sie beim echoBefehl erweitern können, müssen Sie zweimal fortfahren, um zwei Erweiterungen der $xVariablen zu erzwingen :

user@host:~$ x='~/someDirectory'
+ x='~/someDirectory'
user@host:~$ echo "$( eval echo $x )"
++ eval echo '~/someDirectory'
+++ echo /home/user/someDirectory
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$ 

Beachten Sie jedoch, dass je nach Kontext, in dem Sie eine solche Struktur verwenden, unerwünschte Nebenwirkungen auftreten können. Als Faustregel sollten Sie lieber alles vermeiden, was Sie benötigen, evalwenn Sie einen anderen Weg haben.

Wenn Sie das Tilde-Problem im Gegensatz zu anderen Erweiterungen gezielt angehen möchten, ist eine solche Struktur sicherer und portabler:

user@host:~$ x='~/someDirectory'
+ x='~/someDirectory'
user@host:~$ case "$x" in "~/"*)
>     x="${HOME}/${x#"~/"}"
> esac
+ case "$x" in
+ x=/home/user/someDirectory
user@host:~$ echo $x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$ 

Diese Struktur überprüft explizit das Vorhandensein eines Leading ~und ersetzt es durch das Home-Verzeichnis des Benutzers, wenn es gefunden wird.

Nach Ihrem Kommentar x="${HOME}/${x#"~/"}"mag dies in der Tat für jemanden überraschend sein, der nicht in der Shell-Programmierung verwendet wird, aber tatsächlich mit derselben POSIX-Regel verknüpft ist, die ich oben zitiert habe.

Gemäß dem POSIX-Standard erfolgt die Entfernung von Anführungszeichen zuletzt und die Parametererweiterung sehr früh. Somit ${#"~"}wird weit vor der Auswertung der äußeren Anführungszeichen ausgewertet und erweitert. Abwechselnd wie in den Parametererweiterungsregeln definiert:

In jedem Fall, in dem ein Wortwert benötigt wird (basierend auf dem Parameterzustand, wie unten beschrieben), muss das Wort einer Tildeerweiterung, einer Parametererweiterung, einer Befehlssubstitution und einer arithmetischen Erweiterung unterzogen werden.

Daher muss die rechte Seite des #Bedieners korrekt zitiert oder ausgeblendet werden, um eine Tilde-Expansion zu vermeiden.

Um es anders x="${HOME}/${x#"~/"}"auszudrücken: Wenn der Shell-Interpret es betrachtet , sieht er:

  1. ${HOME}und ${x#"~/"}muss erweitert werden.
  2. ${HOME}wird auf den Inhalt der $HOMEVariablen erweitert.
  3. ${x#"~/"}löst eine verschachtelte Erweiterung aus: "~/"wird analysiert, aber in Anführungszeichen als Literal 1 behandelt . Sie hätten hier einfache Anführungszeichen mit demselben Ergebnis verwenden können.
  4. ${x#"~/"}Der Ausdruck selbst wird jetzt erweitert, wodurch das Präfix ~/aus dem Wert von entfernt wird $x.
  5. Das Ergebnis des oben Gesagten ist nun verkettet: die Erweiterung ${HOME}, das Literal /, die Erweiterung ${x#"~/"}.
  6. Das Endergebnis ist in doppelte Anführungszeichen eingeschlossen, wodurch die Wortteilung funktional verhindert wird. Ich sage hier funktional, weil diese doppelten Anführungszeichen technisch nicht erforderlich sind (siehe hier und da zum Beispiel), aber als persönlicher Stil, sobald eine Aufgabe etwas weiter bringt, a=$bfinde ich es normalerweise klarer, doppelte Anführungszeichen hinzuzufügen.

Übrigens, wenn Sie sich die caseSyntax genauer ansehen , werden Sie die "~/"*Konstruktion sehen, die auf dem gleichen Konzept beruht, das x=~/'someDirectory'ich oben erklärt habe (auch hier könnten doppelte und einfache Anführungszeichen austauschbar verwendet werden).

Machen Sie sich keine Sorgen, wenn diese Dinge auf den ersten Blick dunkel erscheinen (vielleicht sogar auf den zweiten oder späteren Blick!). Meiner Meinung nach ist die Parametererweiterung mit Subshells eines der komplexesten Konzepte, die beim Programmieren in Shell-Sprache zu verstehen sind.

Ich weiß, dass einige Leute heftig anderer Meinung sein mögen, aber wenn Sie die Shell-Programmierung genauer lernen möchten, empfehle ich Ihnen, das Advanced Bash Scripting Guide zu lesen : Es lehrt Bash-Scripting, also mit vielen Erweiterungen und Bells-and- pfeift im Vergleich zu POSIX-Shell-Skripten, aber ich fand es gut geschrieben mit vielen praktischen Beispielen. Sobald Sie dies geschafft haben, können Sie sich bei Bedarf leicht auf POSIX-Funktionen beschränken. Ich persönlich denke, dass der direkte Einstieg in den POSIX-Bereich für Anfänger eine unnötig steile Lernkurve darstellt (vergleichen Sie meinen POSIX-Tilde-Ersatz mit @ m0dulars regulärem Bash äquivalent, um eine Vorstellung davon zu bekommen, was ich meine;)!).


1 : Was mich dazu bringt, einen Fehler in Dash zu finden, der die Tilde-Erweiterung hier nicht korrekt implementiert (überprüfbar mit x='~/foo'; echo "${x#~/}"). Die Parametererweiterung ist sowohl für den Benutzer als auch für die Shell-Entwickler selbst ein komplexes Feld!

WhiteWinterWolf
quelle
Wie analysiert die Bash-Shell die Zeile x="${HOME}/${x#"~/"}"? Es sieht aus wie eine Verkettung von drei Strings: "${HOME}/${x#", ~/, und "}". Ermöglicht die Shell verschachtelte doppelte Anführungszeichen, wenn sich das innere Paar doppelter Anführungszeichen in einem ${ }Block befindet?
Andrew
@ Andrew: Ich habe meine Antwort mit zusätzlichen Informationen vervollständigt, die hoffentlich auf Ihren Kommentar eingehen.
WhiteWinterWolf
Danke, das ist eine großartige Antwort. Ich habe eine Menge gelernt, indem ich es gelesen habe. Ich wünschte, ich könnte es mehr als einmal positiv bewerten :)
Andrew
@WhiteWinterWolf: Trotzdem sieht die Shell die verschachtelten Anführungszeichen nicht, was auch immer das Ergebnis ist.
Avp
6

Eine mögliche Antwort:

eval echo "$x"

Da Sie Eingaben aus einer Datei lesen, würde ich dies nicht tun.

Sie können das ~ wie folgt suchen und durch den Wert von $ HOME ersetzen:

x='~/.config'
x="${x//\~/$HOME}"
echo "$x"

Gibt mir:

/home/adrian/.config
m0dular
quelle
Beachten Sie, dass die ${parameter/pattern/string}Erweiterung eine Bash-Erweiterung ist und möglicherweise nicht in anderen Shells verfügbar ist.
WhiteWinterWolf
Wahr. Das OP hat erwähnt, dass er Bash verwendet, daher denke ich, dass dies eine angemessene Antwort ist.
m0dular
Ich stimme zu, solange man sich an Bash hält, warum nicht voll ausnutzen (nicht jeder braucht überall Portabilität), aber es lohnt sich nur, dies für Nicht-Bash-Benutzer zu beachten (mehrere Distributionen werden jetzt zum Beispiel mit Dash anstelle von Bash ausgeliefert ) so sind betroffene Benutzer nicht überrascht.
WhiteWinterWolf
Ich habe mir erlaubt, Ihren Beitrag in meinem Exkurs über Unterschiede zwischen Bash-Erweiterungen und POSIX-Shell-Skripten zu erwähnen, da ich denke, dass Ihre einzeilige Regex-ähnliche Bash-Anweisung im Vergleich zu meiner POSIX- caseStruktur gut zeigt, wie benutzerfreundlicher Bash-Skripte besonders sind für Anfänger.
WhiteWinterWolf