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

前端 未结 15 2173
离开以前
离开以前 2020-11-22 09:34

I have a variable in my bash script whose value is something like this:

~/a/b/c

Note that it is unexpanded tilde. When I do ls -lt on this

相关标签:
15条回答
  • 2020-11-22 09:45

    Simplest: replace 'magic' with 'eval echo'.

    $ eval echo "~"
    /whatever/the/f/the/home/directory/is
    

    Problem: You're going to run into issues with other variables because eval is evil. For instance:

    $ # home is /Users/Hacker$(s)
    $ s="echo SCARY COMMAND"
    $ eval echo $(eval echo "~")
    /Users/HackerSCARY COMMAND
    

    Note that the issue of the injection doesn't happen on the first expansion. So if you were to simply replace magic with eval echo, you should be okay. But if you do echo $(eval echo ~), that would be susceptible to injection.

    Similarly, if you do eval echo ~ instead of eval echo "~", that would count as twice expanded and therefore injection would be possible right away.

    0 讨论(0)
  • 2020-11-22 09:45

    Here is the POSIX function equivalent of Håkon Hægland's Bash answer

    expand_tilde() {
        tilde_less="${1#\~/}"
        [ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less"
        printf '%s' "$tilde_less"
    }
    

    2017-12-10 edit: add '%s' per @CharlesDuffy in the comments.

    0 讨论(0)
  • 2020-11-22 09:46

    You might find this easier to do in python.

    (1) From the unix command line:

    python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' ~/fred
    

    Results in:

    /Users/someone/fred
    

    (2) Within a bash script as a one-off - save this as test.sh:

    #!/usr/bin/env bash
    
    thepath=$(python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' $1)
    
    echo $thepath
    

    Running bash ./test.sh results in:

    /Users/someone/fred
    

    (3) As a utility - save this as expanduser somewhere on your path, with execute permissions:

    #!/usr/bin/env python
    
    import sys
    import os
    
    print os.path.expanduser(sys.argv[1])
    

    This could then be used on the command line:

    expanduser ~/fred
    

    Or in a script:

    #!/usr/bin/env bash
    
    thepath=$(expanduser $1)
    
    echo $thepath
    
    0 讨论(0)
  • 2020-11-22 09:49

    Expanding (no pun intended) on birryree's and halloleo's answers: The general approach is to use eval, but it comes with some important caveats, namely spaces and output redirection (>) in the variable. The following seems to work for me:

    mypath="$1"
    
    if [ -e "`eval echo ${mypath//>}`" ]; then
        echo "FOUND $mypath"
    else
        echo "$mypath NOT FOUND"
    fi
    

    Try it with each of the following arguments:

    '~'
    '~/existing_file'
    '~/existing file with spaces'
    '~/nonexistant_file'
    '~/nonexistant file with spaces'
    '~/string containing > redirection'
    '~/string containing > redirection > again and >> again'
    

    Explanation

    • The ${mypath//>} strips out > characters which could clobber a file during the eval.
    • The eval echo ... is what does the actual tilde expansion
    • The double-quotes around the -e argument are for support of filenames with spaces.

    Perhaps there's a more elegant solution, but this is what I was able to come up with.

    0 讨论(0)
  • 2020-11-22 09:52

    why not delve straight into getting the user's home directory with getent?

    $ getent passwd mike | cut -d: -f6
    /users/mike
    
    0 讨论(0)
  • 2020-11-22 09:54

    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
    }
    
    0 讨论(0)
提交回复
热议问题