Come posso espandere una tilde ~ come parte di una variabile?


12

Quando apro un prompt di bash e digito:

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

Speravo che la quinta riga sopra sarebbe andata + echo /home/myUsername/someDirectory. C'è un modo per fare questo? Nel mio script Bash originale, la variabile x viene effettivamente popolata dai dati di un file di input, tramite un ciclo come questo:

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

Tuttavia, sto ottenendo un risultato simile, con echo '~/someDirectory'invece di echo /home/myUsername/someDirectory.


In ZSH questo è x='~'; print -l ${x} ${~x}. Mi sono arreso dopo aver scavato nel bashmanuale per un po '.
thrig

@thrig: questo non è un bashismo, questo comportamento è POSIX.
WhiteWinterWolf,

Estremamente strettamente correlati (se non un duplicato
Kusalananda

@Kusalananda: Non sono sicuro che si tratti di un duplicato perché la ragione qui è leggermente diversa: l'OP non ha racchiuso $ x tra virgolette durante l'eco.
WhiteWinterWolf,

Non inserire le tilde nel file di input. Problema risolto.
Chepner,

Risposte:


11

Lo standard POSIX impone l'espansione delle parole nel seguente ordine (enfatizzare è mio):

  1. L'espansione della tilde (vedi espansione della tilde), l' espansione dei parametri (vedi espansione dei parametri), la sostituzione dei comandi (vedi sostituzione dei comandi) e l'espansione aritmetica (vedi l'espansione aritmetica) devono essere eseguite dall'inizio alla fine. Vedi l'articolo 5 in Riconoscimento token.

  2. La suddivisione dei campi (vedere Divisione dei campi) deve essere eseguita sulle porzioni dei campi generate dal passaggio 1, a meno che IFS sia nullo.

  3. L'espansione del nome del percorso (vedere Espansione del nome del percorso) deve essere eseguita, a meno che non sia attivo set -f.

  4. La rimozione del preventivo (vedere Rimozione del preventivo) deve sempre essere eseguita per ultima.

L'unico punto che ci interessa qui è il primo: come puoi vedere l'espansione della tilde viene elaborata prima dell'espansione dei parametri:

  1. La shell tenta di espandere una tilde echo $x, non è possibile trovare tilde, quindi procede.
  2. La shell tenta di espandere un parametro echo $x, $xviene trovata ed espansa e la riga di comando diventa echo ~/someDirectory.
  3. L'elaborazione continua, poiché l'espansione della tilde è già stata elaborata, il ~personaggio rimane così com'è.

Usando le virgolette durante l'assegnazione di $x, si chiedeva esplicitamente di non espandere la tilde e trattarla come un normale personaggio. Una cosa che spesso manca è che nei comandi della shell non è necessario citare l'intera stringa, quindi è possibile fare l'espansione proprio durante l'assegnazione della variabile:

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:~$

E puoi anche fare in modo che l'espansione avvenga sulla echoriga di comando fintanto che può accadere prima dell'espansione dei parametri:

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

Se per qualche motivo hai davvero bisogno di influenzare la tilde sulla $xvariabile senza espansione ed essere in grado di espanderla al echocomando, devi procedere in due volte per forzare due espansioni della $xvariabile:

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:~$ 

Tuttavia, tenere presente che, a seconda del contesto in cui si utilizza tale struttura, potrebbe avere effetti collaterali indesiderati. Come regola generale, preferisci evitare di usare qualsiasi cosa richieda evalquando hai un altro modo.

Se si desidera affrontare in modo specifico il problema della tilde rispetto a qualsiasi altro tipo di espansione, tale struttura sarebbe più sicura e portatile:

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:~$ 

Questa struttura controlla esplicitamente la presenza di un lead ~e lo sostituisce con la home directory dell'utente se viene trovata.

A seguito del tuo commento, x="${HOME}/${x#"~/"}"potrebbe essere davvero sorprendente per qualcuno che non viene utilizzato nella programmazione della shell, ma è in effetti collegato alla stessa regola POSIX che ho citato sopra.

Come imposto dallo standard POSIX, la rimozione del preventivo avviene per ultima e l'espansione dei parametri avviene molto presto. Pertanto, ${#"~"}viene valutato e ampliato molto prima della valutazione delle virgolette esterne. A sua volta, come definito nelle Regole di espansione dei parametri :

In ogni caso in cui sia necessario un valore di parola (basato sullo stato del parametro, come descritto di seguito), la parola deve essere sottoposta a espansione tilde, espansione parametro, sostituzione comando ed espansione aritmetica.

Pertanto, il lato destro #dell'operatore deve essere opportunamente citato o sfuggito per evitare l'espansione della tilde.

Quindi, per dirlo diversamente, quando l'interprete della shell guarda x="${HOME}/${x#"~/"}", vede:

  1. ${HOME}e ${x#"~/"}deve essere espanso.
  2. ${HOME}viene espanso al contenuto della $HOMEvariabile.
  3. ${x#"~/"}innesca un'espansione nidificata: "~/"viene analizzata ma, essendo citata, viene trattata come 1 letterale . Avresti potuto usare virgolette singole qui con lo stesso risultato.
  4. ${x#"~/"}l'espressione stessa viene ora espansa, determinando la ~/rimozione del prefisso dal valore di $x.
  5. Il risultato di quanto sopra è ora concatenato: l'espansione ${HOME}, letterale /, l'espansione ${x#"~/"}.
  6. Il risultato finale è racchiuso tra virgolette doppie, impedendo funzionalmente la divisione delle parole. Dico funzionalmente qui perché queste doppie virgolette non sono tecnicamente richieste (vedi qua e per esempio), ma come stile personale non appena un compito ottiene qualcosa oltre, di a=$bsolito trovo più chiaro aggiungere doppie virgolette.

A proposito, se osservi più da vicino la casesintassi, vedrai la "~/"*costruzione che si basa sullo stesso concetto che x=~/'someDirectory'ho spiegato sopra (anche in questo caso, le virgolette doppie e semplici potrebbero essere usate in modo intercambiabile).

Non preoccuparti se queste cose possono sembrare oscure a prima vista (forse anche alla seconda o più tardi!). A mio avviso, l'espansione dei parametri è, con subshells, uno dei concetti più complessi da comprendere durante la programmazione in linguaggio shell.

So che alcune persone potrebbero essere in forte disaccordo, ma se desideri approfondire la programmazione della shell ti incoraggio a leggere la Guida avanzata agli script di Bash : insegna lo script di Bash, quindi con molte estensioni e campane e fischietti rispetto allo script di shell POSIX, ma l'ho trovato ben scritto con un sacco di esempi pratici. Una volta gestito questo, è facile limitarsi alle funzionalità POSIX quando è necessario, personalmente penso che entrare direttamente nel regno POSIX sia una curva di apprendimento ripida non necessaria per i principianti (confronta la mia sostituzione della tilde POSIX con Bash simile a regex di @ m0dular equivale a farsi un'idea di cosa intendo;)!).


1 : Il che mi porta a trovare un bug in Dash che non implementa correttamente l'espansione tilde (usando verificabile x='~/foo'; echo "${x#~/}"). L'espansione dei parametri è un campo complesso sia per l'utente che per gli sviluppatori stessi della shell!


In che modo la shell bash sta analizzando la linea x="${HOME}/${x#"~/"}"? Si presenta come una concatenazione di stringhe 3: "${HOME}/${x#", ~/, e "}". La shell consente virgolette doppie nidificate quando la coppia interna di virgolette doppie si trova all'interno di un ${ }blocco?
Andrew,

@Andrew: ho completato la mia risposta con ulteriori informazioni sperando di indirizzare il tuo commento.
WhiteWinterWolf

Grazie, questa è un'ottima risposta. Ho imparato molto leggendolo. Vorrei poterlo votare più di una volta :)
Andrew,

@WhiteWinterWolf: ancora, shell non vede le virgolette nidificate qualunque sia il risultato.
avp

6

Una possibile risposta:

eval echo "$x"

Dato che stai leggendo l'input da un file, non lo farei.

Puoi cercare e sostituire ~ con il valore di $ HOME, in questo modo:

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

Mi da:

/home/adrian/.config

Si noti che l' ${parameter/pattern/string}espansione è un'estensione Bash e potrebbe non essere disponibile in altre shell.
WhiteWinterWolf,

Vero. L'OP ha menzionato che stava usando Bash, quindi penso che sia una risposta appropriata.
m0dular,

Sono d'accordo, fintanto che uno si attacca a Bash perché non sfruttarne appieno (non tutti hanno bisogno di portabilità ovunque), ma vale la pena notare per gli utenti non Bash (diverse distro ora vengono fornite con Dash anziché Bash, ad esempio ), quindi gli utenti interessati non sono sorpresi.
WhiteWinterWolf,

Mi sono preso la libertà di menzionare il tuo post nella mia digressione sulle differenze tra estensioni di Bash e script di shell POSIX, poiché penso che la tua dichiarazione di regex a riga singola di Bash rispetto alla mia casestruttura POSIX illustri bene come lo script di Bash è più user-friendly specialmente per principianti.
WhiteWinterWolf
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.