Comment développer manuellement une variable spéciale (ex: ~ tilde ) dans bash
J'ai une variable dans mon script bash dont la valeur est quelque chose comme ceci:
~/a/b/c
Notez qu'il s'agit d'un tilde Non expandé. Quand je fais ls-lt sur cette variable (appelez-la $VAR), Je n'obtiens pas un tel répertoire. Je veux laisser bash interpréter / développer cette variable sans l'exécuter. En d'autres termes, je veux que bash exécute eval mais pas la commande évaluée. Est-ce possible dans bash?
Comment ai-je réussi à passer cela dans mon script sans expansion? J'ai passé l'argument en l'entourant avec des guillemets doubles.
Essayez cette commande pour voir ce que je veux dire:
ls -lt "~"
C'est exactement la situation dans laquelle je suis. Je veux que le tilde soit élargi. En d'autres termes, avec quoi dois-je remplacer magic pour rendre ces deux commandes identiques:
ls -lt ~/abc/def/ghi
Et
ls -lt $(magic "~/abc/def/ghi")
Notez que ~ / abc / DEF / ghi peut exister ou non.
13 réponses
En raison de la nature de StackOverflow, Je ne peux pas simplement rendre cette réponse inacceptable, mais dans les années 5 depuis que j'ai posté ceci, il y a eu de bien meilleures réponses que ma réponse rudimentaire et plutôt mauvaise (j'étais jeune, ne me tue pas).
Les autres solutions dans ce fil sont des solutions plus sûres et meilleures. De préférence, j'irais avec l'un de ces deux:
Réponse originale à des fins historiques (mais ne l'utilisez pas)
Si Je ne me trompe pas, "~"
ne sera pas développé par un script bash de cette manière car il est traité comme une chaîne littérale "~"
. Vous pouvez forcer l'expansion via eval
comme ceci.
#!/bin/bash
homedir=~
eval homedir=$homedir
echo $homedir # prints home path
Sinon, utilisez simplement ${HOME}
Si vous voulez le répertoire personnel de l'utilisateur.
Si la variable var
est entrée par l'utilisateur, eval
devrait - pas être utilisé pour étendre le tilde à l'aide de
eval var=$var # Do not use this!
La raison en est: l'utilisateur pourrait par accident (ou par but) taper par exemple {[4] } avec des conséquences désastreuses possibles.
Un meilleur moyen (et plus sûr) est d'utiliser l'expansion des paramètres Bash:
var="${var/#\~/$HOME}"
Me plagier à partir de une réponse antérieure , pour le faire de manière robuste sans les risques de sécurité 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 _ _ _ _ _ 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 qui utilise eval
attentivement:
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 c'est 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
Notez Également que, sous zsh ce serait aussi simple que de echo ${~dangerous_path}
Expansion (sans jeu de mots) sur les réponses de birryree et halloleo: L'approche générale consiste à utiliser eval
, mais elle comporte des 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
Essayer 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
- le
${mypath//>}
supprime les caractères>
qui pourraient bloquer un fichier pendant leeval
. - Le
eval echo ...
est ce que fait l' expansion réelle du tilde - les guillemets doubles autour de l'argument
-e
sont pour la prise en charge des noms de fichiers avec des espaces.
Il y a peut-être une solution plus élégante, mais c'est ce que j'ai pu trouver.
Que diriez-vous de ceci:
path=`realpath "$1"`
Ou:
path=`readlink -f "$1"`
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"
Utilisez simplement eval
correctement: avec validation.
case $1${1%%/*} in
([!~]*|"$1"?*[!-+_.[:alnum:]]*|"") ! :;;
(*/*) set "${1%%/*}" "${1#*/}" ;;
(*) set "$1"
esac&& eval "printf '%s\n' $1${2+/\"\$2\"}"
Voici la fonction POSIX équivalente à la réponse 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 modifier: ajouter '%s'
par @ CharlesDuffy dans les commentaires.
Juste pour étendre la réponse debirryree pour les chemins avec des espaces: vous ne pouvez pas utiliser la commande eval
telle quelle car elle sépare l'évaluation par des espaces. Une solution consiste à remplacer temporairement les espaces 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 de caractères "_spc_"
.
Vous pourriez trouver cela plus facile à faire en python.
(1) depuis 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 en tant que one-off-Enregistrez ceci sous test.sh
:
#!/usr/bin/env bash
thepath=$(python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' $1)
echo $thepath
L'Exécution de bash ./test.sh
résultats:
/Users/someone/fred
(3) en tant qu'utilitaire-enregistrez-le sous expanduser
quelque part sur votre chemin, avec les autorisations d'exécution:
#!/usr/bin/env python
import sys
import os
print os.path.expanduser(sys.argv[1])
Cela pourrait ensuite être utilisé sur la ligne de commande:
expanduser ~/fred
Ou dans un script:
#!/usr/bin/env bash
thepath=$(expanduser $1)
echo $thepath
La plus Simple: remplacer 'magie' avec 'eval echo'.
$ eval echo "~"
/whatever/the/f/the/home/directory/is
Problème:, Vous allez rencontrer des problèmes avec d'autres variables, car eval est le mal. Par exemple:
$ # home is /Users/Hacker$(s)
$ s="echo SCARY COMMAND"
$ eval echo $(eval echo "~")
/Users/HackerSCARY COMMAND
Notez que le problème de l'injection ne se produit pas lors de la première expansion. Donc, si vous deviez simplement remplacer magic
par eval echo
, vous devriez aller bien. Mais si vous faites echo $(eval echo ~)
, ce serait susceptible d'injection.
De même, si vous faites eval echo ~
au lieu de eval echo "~"
, cela compterait deux fois élargi et donc l'injection serait possible tout de suite.