El estándar POSIX impone que la expansión de palabras se realice en el siguiente orden (el énfasis es mío):
La expansión Tilde (ver Expansión Tilde), la expansión de parámetros (ver Expansión de parámetros), la sustitución de comandos (ver Sustitución de comandos) y la expansión aritmética (ver Expansión aritmética) se realizarán, de principio a fin. Ver ítem 5 en Reconocimiento de tokens.
La división de campo (ver División de campo) se realizará en las porciones de los campos generados por el paso 1, a menos que IFS sea nulo.
La expansión de nombre de ruta (ver Expansión de nombre de ruta) se realizará, a menos que el conjunto -f esté vigente.
La eliminación de la cotización (ver Eliminación de la cotización) siempre se realizará en último lugar.
El único punto que nos interesa aquí es el primero: como puede ver, la expansión de tilde se procesa antes de la expansión de parámetros:
- El shell intenta una expansión de tilde
echo $x
, no se encuentra tilde, por lo que continúa.
- El shell intenta expandir un parámetro
echo $x
, $x
se encuentra y se expande y la línea de comando se convierte echo ~/someDirectory
.
- El procesamiento continúa, ya que la expansión de tilde ya se ha procesado, el
~
personaje permanece tal cual.
Al usar las comillas mientras asignaba $x
, solicitaba explícitamente no expandir la tilde y tratarla como un carácter normal. Una cosa que a menudo se pasa por alto es que en los comandos de shell no tiene que citar la cadena completa, por lo que puede hacer que la expansión se realice correctamente durante la asignación de variables:
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:~$
Y también puede hacer que la expansión ocurra en la echo
línea de comandos siempre que pueda ocurrir antes de la expansión de parámetros:
user@host:~$ x='someDirectory'
+ x=someDirectory
user@host:~$ echo ~/$x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
Si por alguna razón realmente necesita afectar la tilde a la $x
variable sin expansión, y poder expandirla con el echo
comando, debe proceder dos veces para forzar $x
que ocurran dos expansiones de la variable:
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:~$
Sin embargo, tenga en cuenta que, según el contexto en el que utilice dicha estructura, puede tener efectos secundarios no deseados. Como regla general, prefiera evitar usar cualquier cosa que requiera eval
cuando tenga otra forma.
Si desea abordar específicamente el problema de tilde en lugar de cualquier otro tipo de expansión, dicha estructura sería más segura y portátil:
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:~$
Esta estructura verifica explícitamente la presencia de un líder ~
y lo reemplaza con el directorio de inicio del usuario si se encuentra.
Después de su comentario, x="${HOME}/${x#"~/"}"
puede ser sorprendente para alguien que no se utiliza en la programación de shell, pero de hecho está vinculado a la misma regla POSIX que cité anteriormente.
Según lo impuesto por el estándar POSIX, la eliminación de comillas ocurre en último lugar y la expansión de parámetros ocurre muy temprano. Por lo tanto, ${#"~"}
se evalúa y se expande mucho antes de la evaluación de las comillas externas. Por turnos, como se define en las reglas de expansión de parámetros :
En cada caso que se necesita un valor de palabra (basado en el estado del parámetro, como se describe a continuación), la palabra se someterá a una expansión de tilde, expansión de parámetros, sustitución de comandos y expansión aritmética.
Por lo tanto, el lado derecho del #
operador debe ser citado o escapado correctamente para evitar la expansión de la tilde.
Entonces, para decirlo de manera diferente, cuando el intérprete de shell mira x="${HOME}/${x#"~/"}"
, ve:
${HOME}
y ${x#"~/"}
debe ser expandido.
${HOME}
se expande al contenido de la $HOME
variable.
${x#"~/"}
desencadena una expansión anidada: "~/"
se analiza pero, al citarse, se trata como un literal 1 . Podría haber usado comillas simples aquí con el mismo resultado.
${x#"~/"}
la expresión en sí ahora se expande, lo que hace que el prefijo ~/
se elimine del valor de $x
.
- El resultado de lo anterior ahora se concatena: la expansión de
${HOME}
, el literal /
, la expansión ${x#"~/"}
.
- El resultado final está encerrado entre comillas dobles, evitando funcionalmente la división de palabras. Digo funcionalmente aquí porque estas comillas dobles no son técnicamente necesarias (ver aquí y allá, por ejemplo), pero como un estilo personal tan pronto como una tarea supera algo
a=$b
, generalmente encuentro más claro agregar comillas dobles.
Por cierto, si observa más de cerca la case
sintaxis, verá la "~/"*
construcción que se basa en el mismo concepto x=~/'someDirectory'
que expliqué anteriormente (aquí nuevamente, las comillas dobles y simples podrían usarse indistintamente).
No se preocupe si estas cosas pueden parecer oscuras a primera vista (¡tal vez incluso a la segunda o más tarde!). En mi opinión, la expansión de parámetros es, con subcapas, uno de los conceptos más complejos a la hora de programar en lenguaje shell.
Sé que algunas personas pueden estar en total desacuerdo, pero si desea aprender más a fondo sobre la programación de shell, le animo a leer la Guía avanzada de secuencias de comandos Bash : enseña la secuencia de comandos Bash, así que con muchas extensiones y campanas. silbidos en comparación con las secuencias de comandos de shell POSIX, pero lo encontré bien escrito con muchos ejemplos prácticos. Una vez que maneje esto, es fácil restringirse a las funciones POSIX cuando lo necesite, personalmente creo que ingresar directamente en el reino POSIX es una curva de aprendizaje empinada innecesaria para principiantes (compare mi reemplazo de tilde POSIX con Bash tipo regex de @ m0dular equivalente a tener una idea de lo que quiero decir;)!).
1 : Lo que me lleva a encontrar un error en Dash que no implementa la expansión de tilde aquí correctamente (uso comprobable x='~/foo'; echo "${x#~/}"
). ¡La expansión de parámetros es un campo complejo tanto para el usuario como para los propios desarrolladores de shell!
x='~'; print -l ${x} ${~x}
. Me di por vencido después de revisar elbash
manual por un tiempo.