Comment développer manuellement une variable particulière (ex: ~ tilde) en bash
-
09-10-2019 - |
Question
J'ai une variable dans mon script bash dont la valeur est quelque chose comme ceci:
~/a/b/c
Notez qu'il est tilde non dilatée. Quand je fais ls -LT sur cette variable (appeler $ VAR), je reçois pas de répertoire. Je veux laisser bash interpréter / étendre cette variable sans l'exécuter. En d'autres termes, je veux bash à courir eval mais pas courir la commande évaluée. Est-ce possible en bash?
Comment ai-je réussi à passer ceci dans mon script sans extension? J'ai passé l'argument qui l'entoure avec des guillemets doubles.
Essayez cette commande pour voir ce que je veux dire:
ls -lt "~"
Ceci est exactement la situation que je suis. Je veux que le tilde soit élargi. En d'autres termes, que dois-je remplacer la magie avec pour rendre ces deux commandes identiques:
ls -lt ~/abc/def/ghi
et
ls -lt $(magic "~/abc/def/ghi")
Notez que ~ / abc / def / ghi peut ou peut ne pas exister.
La solution
En raison de la nature de StackOverflow, je ne peux pas faire cette réponse unaccepted, mais dans les 5 années écoulées depuis que je posté il y a eu des réponses beaucoup mieux que ma réponse, certes rudimentaire et assez mauvais (j'étais jeune, ne me tuez pas).
Les autres solutions dans ce fil sont plus sûrs et de meilleures solutions. De préférence, je partirais avec l'une de ces deux:
- La solution Charle Duffy
- Håkon Hægland
Réponse originale à des fins historiques (mais s'il vous plaît ne pas utiliser)
Si je ne me trompe pas, "~"
ne sera pas étendu par un script bash de cette manière parce qu'elle est traitée comme une "~"
chaîne littérale. Vous pouvez forcer l'expansion via eval
comme celui-ci.
#!/bin/bash
homedir=~
eval homedir=$homedir
echo $homedir # prints home path
Sinon, il suffit d'utiliser ${HOME}
si vous voulez que le répertoire personnel de l'utilisateur.
Autres conseils
Si la var
variable est entrée par l'utilisateur, eval
devrait pas être utilisé pour étendre le tilde en utilisant
eval var=$var # Do not use this!
La raison en est. L'utilisateur pourrait par hasard (ou en fin) par exemple de type var="$(rm -rf $HOME/)"
avec des conséquences désastreuses possibles
Une meilleure (et plus sûr) consiste à utiliser l'expansion du paramètre bash:
var="${var/#\~/$HOME}"
Plagarizing moi-même de de réponse avant, de le faire sans la sécurité avec vigueur les risques associés à 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 -r _ _ _ _ _ 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%:}"
}
... utilisé comme ...
path=$(expandPath '~/hello')
Alternativement, une approche plus simple que les utilisations eval
soigneusement:
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
}
Un moyen sûr d'utiliser eval est "$(printf "~/%q" "$dangerous_path")"
. Notez que spécifique bash.
#!/bin/bash
relativepath=a/b/c
eval homedir="$(printf "~/%q" "$relativepath")"
echo $homedir # prints home path
Voir cette question pour plus de détails
En outre, notez que sous zsh ce serait comme aussi simple que echo ${~dangerous_path}
Que diriez-vous ceci:
path=`realpath "$1"`
Ou:
path=`readlink -f "$1"`
L'expansion (sans jeu de mots) sur les réponses de birryree et de halloleo: L'approche générale est d'utiliser eval
, mais il est livré avec quelques mises en garde importantes, à savoir les espaces et la redirection de sortie (>
) dans la variable. Ce qui suit semble fonctionner pour moi:
mypath="$1"
if [ -e "`eval echo ${mypath//>}`" ]; then
echo "FOUND $mypath"
else
echo "$mypath NOT FOUND"
fi
Essayez avec chacun des arguments suivants:
'~'
'~/existing_file'
'~/existing file with spaces'
'~/nonexistant_file'
'~/nonexistant file with spaces'
'~/string containing > redirection'
'~/string containing > redirection > again and >> again'
Explication
- Les bandes
${mypath//>}
les caractères de>
qui pourrait CLOBBER un fichier au cours de laeval
. - Le
eval echo ...
est ce que fait l'expansion du tilde réelle - Les guillemets doubles autour de l'argument
-e
sont pour le soutien des noms de fichiers avec des espaces.
Peut-être il y a une solution plus élégante, mais ce que je pouvais trouver.
Je crois que c'est ce que vous cherchez
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.$$
}
Exemple d'utilisation:
$ magic ~nobody/would/look/here
/var/empty/would/look/here
$ magic ~invalid/this/will/not/expand
~invalid/this/will/not/expand
Voici ma solution:
#!/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"
Il suffit d'utiliser eval
correctement. Avec la validation
case $1${1%%/*} in
([!~]*|"$1"?*[!-+_.[:alnum:]]*|"") ! :;;
(*/*) set "${1%%/*}" "${1#*/}" ;;
(*) set "$1"
esac&& eval "printf '%s\n' $1${2+/\"\$2\"}"
Voici la fonction POSIX équivalent de Bash Håkon Hægland réponse
expand_tilde() {
tilde_less="${1#\~/}"
[ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less"
printf '%s' "$tilde_less"
}
10/12/2017 modifier:. Ajouter '%s'
par @CharlesDuffy dans les commentaires
Juste pour étendre birryree de réponse pour les chemins avec des espaces: Vous ne pouvez pas utiliser la commande eval
comme cela est parce qu'il seperates évaluation par des espaces. Une solution consiste à remplacer les espaces temporairement pour la commande 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
Cet exemple repose bien sûr sur l'hypothèse que mypath
ne contient jamais la séquence char "_spc_"
.
Vous trouverez peut-être ce plus facile à faire en python.
(1) A partir de la ligne de commande unix:
python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' ~/fred
Résultats dans:
/Users/someone/fred
(2) Dans un script bash comme unique - enregistrer comme test.sh
:
#!/usr/bin/env bash
thepath=$(python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' $1)
echo $thepath
Exécution des résultats de bash ./test.sh
dans:
/Users/someone/fred
(3) En tant que service public - enregistrer comme un endroit de expanduser
sur votre chemin, avec des autorisations d'exécution:
#!/usr/bin/env python
import sys
import os
print os.path.expanduser(sys.argv[1])
Cela pourrait alors être utilisé sur la ligne de commande:
expanduser ~/fred
Ou dans un script:
#!/usr/bin/env bash
thepath=$(expanduser $1)
echo $thepath
Simplest :. Remplacer 'magique' par 'eval echo'
$ eval echo "~"
/whatever/the/f/the/home/directory/is
Problème: Vous allez rencontrer des problèmes avec d'autres variables parce eval est mal. Par exemple:
$ # home is /Users/Hacker$(s)
$ s="echo SCARY COMMAND"
$ eval echo $(eval echo "~")
/Users/HackerSCARY COMMAND
Notez que la question de l'injection ne se produit pas sur la première extension. Donc, si vous deviez simplement remplacer magic
avec eval echo
, vous devriez être bien. Mais si vous faites echo $(eval echo ~)
, ce serait sensible à l'injection.
De même, si vous le faites eval echo ~
au lieu de eval echo "~"
, qui compterait jusqu'à deux fois élargi et donc l'injection serait juste possible loin.
Je l'ai fait avec une substitution de paramètre variable après avoir lu dans le chemin à l'aide -e lecture (entre autres). Ainsi, la boîte utilisateur tabulations compléter le chemin, et si l'utilisateur entre un chemin ~ il obtient triée.
read -rep "Enter a path: " -i "${testpath}" testpath
testpath="${testpath/#~/${HOME}}"
ls -al "${testpath}"
L'avantage est que s'il n'y a pas de rien tilde arrive à la variable, et s'il y a un tilde, mais pas dans la première position, il est également ignoré.
(I comprennent les -i pour lire depuis que je l'utiliser dans une boucle afin que l'utilisateur peut fixer le chemin s'il y a un problème.)