Cómo expandir manualmente una variable especial (por ejemplo: ~ tilde) en bash

Tengo una variable en mi script bash cuyo valor es algo como esto:

~/a/b/c 

Tenga en cuenta que es tilde no expandida. Cuando hago ls -lt en esta variable (llámala $ VAR), no obtengo tal directorio. Quiero dejar que bash interprete / expanda esta variable sin ejecutarla. En otras palabras, quiero que bash ejecute eval pero no ejecute el comando evaluado. ¿Es esto posible en bash?

¿Cómo logré pasar esto a mi script sin expansión? Pasé la discusión rodeándola con comillas dobles.

Prueba este comando para ver lo que quiero decir:

 ls -lt "~" 

Esta es exactamente la situación en la que estoy. Quiero que la tilde se expanda. En otras palabras, ¿con qué debería reemplazar la magia para que estos dos comandos sean idénticos?

 ls -lt ~/abc/def/ghi 

y

 ls -lt $(magic "~/abc/def/ghi") 

Tenga en cuenta que ~ / abc / def / ghi puede existir o no.

Debido a la naturaleza de StackOverflow, no puedo simplemente dejar esta respuesta inaceptable, pero en los 5 años transcurridos desde que publiqué esto, ha habido respuestas mucho mejores que mi respuesta ciertamente rudimentaria y bastante mala (yo era joven, no mate yo).

Las otras soluciones en este hilo son soluciones más seguras y mejores. Preferiblemente, iría con cualquiera de estos dos:

  • La solución de Duffy de Charle
  • La solución de Håkon Hægland

Respuesta original para fines históricos (pero no la use)

Si no me equivoco, "~" no será expandido por un script bash de esa manera porque se trata como una cadena literal "~" . Puede forzar la expansión a través de eval como este.

 #!/bin/bash homedir=~ eval homedir=$homedir echo $homedir # prints home path 

Alternativamente, simplemente use ${HOME} si quiere el directorio de inicio del usuario.

Si la variable var es ingresada por el usuario, eval no debe usarse para expandir la tilde usando

 eval var=$var # Do not use this! 

La razón es: el usuario podría por accidente (o por propósito) escribir por ejemplo var="$(rm -rf $HOME/)" con posibles consecuencias desastrosas.

Una forma mejor (y más segura) es usar la expansión del parámetro Bash:

 var="${var/#\~/$HOME}" 

Placerizarme a mí mismo de una respuesta anterior , para hacer esto de forma robusta sin los riesgos de seguridad asociados con eval :

 expandPath() { local path local -a pathElements resultPathElements IFS=':' read -r -a pathElements <<<"$1" : "${pathElements[@]}" for path in "${pathElements[@]}"; do : "$path" case $path in "~+"/*) path=$PWD/${path#"~+/"} ;; "~-"/*) path=$OLDPWD/${path#"~-/"} ;; "~"/*) path=$HOME/${path#"~/"} ;; "~"*) username=${path%%/*} username=${username#"~"} IFS=: read _ _ _ _ _ homedir _ < <(getent passwd "$username") if [[ $path = */* ]]; then path=${homedir}/${path#*/} else path=$homedir fi ;; esac resultPathElements+=( "$path" ) done local result printf -v result '%s:' "${resultPathElements[@]}" printf '%s\n' "${result%:}" } 

...Usado como...

 path=$(expandPath '~/hello') 

Alternativamente, un enfoque más simple que usa eval cuidado:

 expandPath() { case $1 in ~[+-]*) local content content_q printf -v content_q '%q' "${1:2}" eval "content=${1:0:2}${content_q}" printf '%s\n' "$content" ;; ~*) local content content_q printf -v content_q '%q' "${1:1}" eval "content=~${content_q}" printf '%s\n' "$content" ;; *) printf '%s\n' "$1" ;; esac } 

Una forma segura de usar eval es "$(printf "~/%q" "$dangerous_path")" . Tenga en cuenta que es específico de bash.

 #!/bin/bash relativepath=a/b/c eval homedir="$(printf "~/%q" "$relativepath")" echo $homedir # prints home path 

Ver esta pregunta para más detalles

Además, tenga en cuenta que bajo zsh esto sería tan simple como echo ${~dangerous_path}

Expandir (sin juego de palabras) en las respuestas de birryree y halloleo: El enfoque general es usar eval , pero viene con algunas advertencias importantes, a saber, espacios y redirección de salida ( > ) en la variable. Lo siguiente parece funcionar para mí:

 mypath="$1" if [ -e "`eval echo ${mypath//>}`" ]; then echo "FOUND $mypath" else echo "$mypath NOT FOUND" fi 

Pruébalo con cada uno de los siguientes argumentos:

 '~' '~/existing_file' '~/existing file with spaces' '~/nonexistant_file' '~/nonexistant file with spaces' '~/string containing > redirection' '~/string containing > redirection > again and >> again' 

Explicación

  • Los ${mypath//>} eliminan los caracteres que podrían dañar un archivo durante la eval .
  • El eval echo ... es lo que hace la expansión real de tilde
  • Las comillas dobles alrededor del argumento -e son para apoyar nombres de archivos con espacios.

Tal vez hay una solución más elegante, pero esto es lo que pude encontrar.

Qué tal esto:

 path=`realpath "$1"` 

O:

 path=`readlink -f "$1"` 

Creo que esto es lo que estás buscando

 magic() { # returns unexpanded tilde express on invalid user local _safe_path; printf -v _safe_path "%q" "$1" eval "ln -sf ${_safe_path#\\} /tmp/realpath.$$" readlink /tmp/realpath.$$ rm -f /tmp/realpath.$$ } 

Ejemplo de uso:

 $ magic ~nobody/would/look/here /var/empty/would/look/here $ magic ~invalid/this/will/not/expand ~invalid/this/will/not/expand 

Aquí está mi solución:

 #!/bin/bash expandTilde() { local tilde_re='^(~[A-Za-z0-9_.-]*)(.*)' local path="$*" local pathSuffix= if [[ $path =~ $tilde_re ]] then # only use eval on the ~username portion ! path=$(eval echo ${BASH_REMATCH[1]}) pathSuffix=${BASH_REMATCH[2]} fi echo "${path}${pathSuffix}" } result=$(expandTilde "$1") echo "Result = $result" 

Solo use eval correctamente: con validación.

 case $1${1%%/*} in ([!~]*|"$1"?*[!-+_.[:alnum:]]*|"") ! :;; (*/*) set "${1%%/*}" "${1#*/}" ;; (*) set "$1" esac&& eval "printf '%s\n' $1${2+/\"\$2\"}" 

Aquí está la función POSIX equivalente a la respuesta Bash de Håkon Hægland

 expand_tilde() { tilde_less="${1#\~/}" [ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less" printf '%s' "$tilde_less" } 

2017-12-10 editar: agrega '%s' por @CharlesDuffy en los comentarios.

Solo para extender la respuesta de birryree para rutas con espacios: No puede usar el comando eval como está porque separa la evaluación por espacios. Una solución es reemplazar espacios temporalmente para el comando eval:

 mypath="~/a/b/c/Something With Spaces" expandedpath=${mypath// /_spc_} # replace spaces eval expandedpath=${expandedpath} # put spaces back expandedpath=${expandedpath//_spc_/ } echo "$expandedpath" # prints eg /Users/fred/a/b/c/Something With Spaces" ls -lt "$expandedpath" # outputs dir content 

Este ejemplo se basa, por supuesto, en la suposición de que mypath nunca contiene la secuencia char "_spc_" .

Puede encontrar esto más fácil de hacer en Python.

(1) Desde la línea de comando de Unix:

 python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' ~/fred 

Resultados en:

 /Users/someone/fred 

(2) Dentro de un script bash como único: guarde esto como test.sh :

 #!/usr/bin/env bash thepath=$(python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' $1) echo $thepath 

Ejecutar bash ./test.sh resultados en:

 /Users/someone/fred 

(3) Como utilidad: guárdelo como expanduser algún lugar de su ruta, con permisos de ejecución:

 #!/usr/bin/env python import sys import os print os.path.expanduser(sys.argv[1]) 

Esto podría ser utilizado en la línea de comando:

 expanduser ~/fred 

O en una secuencia de comandos:

 #!/usr/bin/env bash thepath=$(expanduser $1) echo $thepath 

Lo más simple : reemplace ‘magia’ con ‘eval echo’.

 $ eval echo "~" /whatever/the/f/the/home/directory/is 

Problema: Vas a encontrar problemas con otras variables porque eval es malo. Por ejemplo:

 $ # home is /Users/Hacker$(s) $ s="echo SCARY COMMAND" $ eval echo $(eval echo "~") /Users/HackerSCARY COMMAND 

Tenga en cuenta que el problema de la inyección no ocurre en la primera expansión. Entonces, si reemplazases simplemente magic con eval echo , deberías estar bien. Pero si haces echo $(eval echo ~) , eso sería susceptible de inyección.

De forma similar, si realiza el eval echo ~ lugar del eval echo "~" de eval echo "~" , eso contaría como dos veces expandido y, por lo tanto, la inyección sería posible de inmediato.