web-dev-qa-db-fra.com

Comment développer manuellement une variable spéciale (ex: ~ tilde) dans bash

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.

119
madiyaan damha

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.

88
wkl

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}"
96
Håkon Hægland

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
}
20
Charles Duffy

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}

9
eddygeek

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'

Explication

  • Le ${mypath//>} se déshabille > caractères pouvant endommager un fichier pendant le eval.
  • Le eval echo ... est ce que fait l’extension réelle du tilde
  • Les doubles guillemets autour du -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.

7
Noach Magedman

Que dis-tu de ça:

path=`realpath "$1"`

Ou:

path=`readlink -f "$1"`
7
Jay

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
Orwellophile

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.

1
go2null

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
Gino

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
mikeserv

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.)

0
JamesIsIn

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

0
halloleo

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
0
Chris Johnson

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.

0
Karim Alibhai