programas - scripts bash ejemplos
Cómo expandir manualmente una variable especial(por ejemplo: ~ tilde) en bash (13)
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.
Aquí está la función POSIX equivalente a la answer 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.
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"
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
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 laeval
. - 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.
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
}
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
Qué tal esto:
path=`realpath "$1"`
O:
path=`readlink -f "$1"`
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}"
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 e.g. /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_"
.
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/"}"
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}
Debido a la naturaleza de , 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.
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.