How to manually expand a special variable (ex: ~ t

2018-12-31 15:43发布

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 variable (call it $VAR), I get no such directory. I want to let bash interpret/expand this variable without executing it. In other words, I want bash to run eval but not run the evaluated command. Is this possible in bash?

How did I manage to pass this into my script without expansion? I passed the argument in surrounding it with double quotes.

Try this command to see what I mean:

ls -lt "~"

This is exactly the situation I am in. I want the tilde to be expanded. In other words, what should I replace magic with to make these two commands identical:

ls -lt ~/abc/def/ghi

and

ls -lt $(magic "~/abc/def/ghi")

Note that ~/abc/def/ghi may or may not exist.

13条回答
孤独寂梦人
2楼-- · 2018-12-31 16:08

A safe way to use eval is "$(printf "~/%q" "$dangerous_path")". Note that is bash specific.

#!/bin/bash

relativepath=a/b/c
eval homedir="$(printf "~/%q" "$relativepath")"
echo $homedir # prints home path

See this question for details

Also, note that under zsh this would be as as simple as echo ${~dangerous_path}

查看更多
流年柔荑漫光年
3楼-- · 2018-12-31 16:11

How about this:

path=`realpath "$1"`

Or:

path=`readlink -f "$1"`
查看更多
栀子花@的思念
4楼-- · 2018-12-31 16:11

I believe this is what you're looking for

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.$$
}

Example usage:

$ magic ~nobody/would/look/here
/var/empty/would/look/here

$ magic ~invalid/this/will/not/expand
~invalid/this/will/not/expand
查看更多
梦醉为红颜
5楼-- · 2018-12-31 16:13

Just use eval correctly: with validation.

case $1${1%%/*} in
([!~]*|"$1"?*[!-+_.[:alnum:]]*|"") ! :;;
(*/*)  set "${1%%/*}" "${1#*/}"       ;;
(*)    set "$1" 
esac&& eval "printf '%s\n' $1${2+/\"\$2\"}"
查看更多
泪湿衣
6楼-- · 2018-12-31 16:15

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/bash

homedir=~
eval homedir=$homedir
echo $homedir # prints home path

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

查看更多
浪荡孟婆
7楼-- · 2018-12-31 16:19

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.

查看更多
登录 后发表回答