How to manually expand a special variable (ex: ~ tilde) in bash How to manually expand a special variable (ex: ~ tilde) in bash bash bash

How to manually expand a special variable (ex: ~ tilde) in bash


If the variable var is input by the user, eval should not be used to expand the tilde using

eval var=$var  # Do not use this!

The reason is: the user could by accident (or by purpose) type for example var="$(rm -rf $HOME/)" with possible disastrous consequences.

A better (and safer) way is to use Bash parameter expansion:

var="${var/#\~/$HOME}"


Due to the nature of StackOverflow, I can't just make this answer unaccepted, but in the intervening 5 years since I posted this there have been far better answers than my admittedly rudimentary and pretty bad answer (I was young, don't kill me).

The other solutions in this thread are safer and better solutions. Preferably, I'd go with either of these two:


Original answer for historic purposes (but please don't use this)

If I'm not mistaken, "~" will not be expanded by a bash script in that manner because it is treated as a literal string "~". You can force expansion via eval like this.

#!/bin/bashhomedir=~eval homedir=$homedirecho $homedir # prints home path

Alternatively, just use ${HOME} if you want the user's home directory.


Plagarizing myself from a prior answer, to do this robustly without the security risks associated with 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%:}"}

...used as...

path=$(expandPath '~/hello')

Alternately, a simpler approach that uses eval carefully:

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}