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.

101
demandé sur madiyaan damha 2010-10-19 01:54:23

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.

77
répondu wkl 2017-05-23 12:10:28

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}"
83
répondu Håkon Hægland 2014-12-15 14:17:35

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
}
17
répondu Charles Duffy 2015-08-21 14:03:10

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}

8
répondu eddygeek 2017-05-23 10:31:12

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 le eval.
  • 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.

7
répondu Noach Magedman 2015-03-12 10:14:30

Que diriez-vous de ceci:

path=`realpath "$1"`

Ou:

path=`readlink -f "$1"`
6
répondu Jay 2012-08-14 11:10:13

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
2
répondu Orwellophile 2015-08-25 12:41:05

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"
1
répondu Gino 2015-08-21 18:03:53

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\"}"
1
répondu mikeserv 2015-12-13 04:49:57

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.

1
répondu go2null 2017-12-10 19:42:53

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_".

0
répondu halloleo 2017-05-23 11:47:16

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
0
répondu Chris Johnson 2015-10-28 14:39:05

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.

0
répondu Karim Alibhai 2017-01-25 01:15:02