J'ai une variable dans mon script bash dont la valeur ressemble à ceci:
~/a/b/c
Notez qu'il s'agit d'un tilde non développé. 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 à Bash?
Comment ai-je réussi à passer cela dans mon script sans expansion? J'ai passé l'argument en l'entourant de guillemets doubles.
Essayez cette commande pour voir ce que je veux dire:
ls -lt "~"
C’est exactement la situation dans laquelle je me trouve. Je veux que le tilde soit développé. En d'autres termes, avec quoi devrais-je remplacer la magie 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 non exister.
En raison de la nature de StackOverflow, je ne peux pas simplement rendre cette réponse inacceptable, mais au cours des cinq années qui se sont écoulées depuis que j'ai posté cette publication, il y a eu de bien meilleures réponses que ma réponse, certes rudimentaire et plutôt mauvaise. (J'étais jeune, ne me tuez pas.)
Les autres solutions de 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 veuillez ne pas l'utiliser)
Si je ne me trompe pas, "~"
ne sera pas développé de cette manière par un script bash, 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 de base de l'utilisateur.
Si la variable var
est entrée par l'utilisateur, eval
devrait not être utilisé pour développer le tilde à l'aide de
eval var=$var # Do not use this!
La raison en est que l'utilisateur pourrait taper par accident (ou par but) par exemple var="$(rm -rf $HOME/)"
avec des conséquences désastreuses.
Une méthode plus efficace (et plus sûre) consiste à utiliser le développement de paramètres Bash:
var="${var/#\~/$HOME}"
En me plaçant de ne réponse préalable , 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 -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 qui utilise eval
avec précaution:
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, cela serait aussi simple que echo ${~dangerous_path}
Développer (sans jeu de mots) les réponses de birryree et halloleo: L’approche générale consiste à utiliser eval
, mais elle comporte quelques mises en garde importantes, à savoir les espaces et la redirection des sorties (>
) 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-le 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'
${mypath//>}
se déshabille >
caractères pouvant endommager un fichier pendant le eval
.eval echo ...
est ce que fait l’extension réelle du tilde-e
argument sont pour le support des noms de fichiers avec des espaces.Il existe peut-être une solution plus élégante, mais c’est ce que j’ai pu trouver.
Que dis-tu de ça:
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 l'équivalent POSIX de la fonction Bash de 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.
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\"}"
Je l'ai fait avec une substitution de paramètre variable après avoir lu le chemin en utilisant read -e (entre autres). Ainsi, l'utilisateur peut compléter le chemin par des tabulations et, s'il entre un ~ chemin, il est trié.
read -rep "Enter a path: " -i "${testpath}" testpath
testpath="${testpath/#~/${HOME}}"
ls -al "${testpath}"
L'avantage supplémentaire est que s'il n'y a pas de tilde, rien ne se produit, et s'il y a un tilde mais pas dans la première position, il est également ignoré.
(J'inclus le -i pour la lecture puisque je l'utilise dans une boucle afin que l'utilisateur puisse corriger le chemin s'il y a un problème.)
Juste pour étendre la réponse de birryree pour les chemins avec des espaces: Vous ne pouvez pas utiliser la commande eval
telle quelle car elle sépare l'évaluation 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 trouverez peut-être cela plus facile à faire en python.
(1) À partir de la ligne de commande unix:
python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' ~/fred
Résulte en:
/Users/someone/fred
(2) Dans un script bash en tant que script unique - enregistrez-le sous le nom test.sh
:
#!/usr/bin/env bash
thepath=$(python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' $1)
echo $thepath
Fonctionnement bash ./test.sh
résulte en:
/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 alors être utilisé sur la ligne de commande:
expanduser ~/fred
Ou dans un script:
#!/usr/bin/env bash
thepath=$(expanduser $1)
echo $thepath
Simplest: remplacez 'magic' par '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 diabolique. 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
, Tout devrait bien se passer. Mais si vous faites echo $(eval echo ~)
, vous risquez une injection.
De même, si vous faites eval echo ~
Au lieu de eval echo "~"
, Cela comptera comme deux fois plus et l'injection serait donc possible immédiatement.