如何在bash中手动扩展一个特殊的variables(例如:〜tilde)

我在我的bash脚本中有一个variables,其值是这样的:

~/a/b/c 

请注意,这是未扩展的代字号。 当我做这个variables的ls -lt(称为$ VAR)时,我没有得到这样的目录。 我想让bash解释/展开这个variables而不执行它。 换句话说,我想bash运行eval,但不运行评估的命令。 这是可能的bash?

我怎样才能在没有扩展的情况下将其传递给我的脚本? 我用双引号括住了这个论点。

试试这个命令来看看我的意思:

 ls -lt "~" 

这正是我所处的情况。我想要扩展波浪号。 换句话说,我应该用什么来取代魔术,使这两个命令相同:

 ls -lt ~/abc/def/ghi 

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

请注意〜/ abc / def / ghi可能存在也可能不存在。

由于StackOverflow的本质,我不能只是让这个答案不被接受,但是在我发布这个5年之后,答案已经比我公认的基本和非常糟糕的答案要好得多了(我还年轻,不要杀我)。

这个线程中的其他解决scheme是更安全和更好的解决scheme。 最好,我会去与这两个之一:

  • 查理的达菲的解决scheme
  • HåkonHægland的解决scheme

历史用途的原始答案(但请不要使用这个)

如果我没有弄错,那么"~"不会被bash脚本以这种方式扩展,因为它被当作string"~" 。 你可以像这样通过eval强制扩展。

 #!/bin/bash homedir=~ eval homedir=$homedir echo $homedir # prints home path 

或者,如果您想要用户的主目录,只需使用${HOME}

如果variablesvar由用户input,则不应使用eval来扩展波浪线

 eval var=$var # Do not use this! 

原因是:用户可能意外地(或按目的)键入例如var="$(rm -rf $HOME/)" ,可能造成灾难性的后果。

更好(更安全)的方法是使用Bash参数扩展:

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

从先前的答案中提出自己的看法,在没有与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%:}" } 

…用作…

 path=$(expandPath '~/hello') 

或者,一个简单的方法,使用eval仔细:

 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 } 

在birryree和halloleo的答案上扩展(没有双关语意思):一般的方法是使用eval ,但它带有一些重要的注意事项,即variables中的空格和输出redirect( > )。 以下似乎为我工作:

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

尝试以下每个参数:

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

说明

  • ${mypath//>}会去掉在eval期间可能会破坏文件的字符。
  • eval echo ...是什么实际的波浪扩展
  • -e参数的双引号用于支持带空格的文件名。

也许有一个更优雅的解决scheme,但这是我能够想出的。

使用eval的安全方法是"$(printf "~/%q" "$dangerous_path")" 。 请注意,是特定的bash。

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

看到这个问题的细节

另外,请注意,在zsh下,这将如echo ${~dangerous_path}

这个怎么样:

 path=`realpath "$1"` 

要么:

 path=`readlink -f "$1"` 

我相信这是你要找的

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

用法示例:

 $ magic ~nobody/would/look/here /var/empty/would/look/here $ magic ~invalid/this/will/not/expand ~invalid/this/will/not/expand 

这是我的解决scheme:

 #!/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" 

只要正确使用eval :validation。

 case $1${1%%/*} in ([!~]*|"$1"?*[!-+_.[:alnum:]]*|"") ! :;; (*/*) set "${1%%/*}" "${1#*/}" ;; (*) set "$1" esac&& eval "printf '%s\n' $1${2+/\"\$2\"}" 

只是为了扩展birryree对空间path的回答:你不能按原样使用eval命令,因为它是用空格分隔的。 一种解决方法是暂时replaceeval命令的空格:

 mypath="~/a/b/c/Something With Spaces" expandedpath=${mypath// /_spc_} # replace spaces eval expandedpath=${expandedpath} # put spaces back expandedpath=${expandedpath//_spc_/ } echo "$expandedpath" # prints eg /Users/fred/a/b/c/Something With Spaces" ls -lt "$expandedpath" # outputs dir content 

这个例子当然依赖于mypath从不包含char序列"_spc_"的假设。

你可能会发现这在Python中更容易做到。

(1)从unix命令行:

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

结果是:

 /Users/someone/fred 

(2)在一个bash脚本中作为一次性 – 保存为test.sh

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

运行bash ./test.sh导致:

 /Users/someone/fred 

(3)作为一个实用程序 – 保存为您的path上某处的expanduser ,具有执行权限:

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

这可以在命令行中使用:

 expanduser ~/fred 

或者在一个脚本中:

 #!/usr/bin/env bash thepath=$(expanduser $1) echo $thepath 

这里是HåkonHægland的Bash 答案的相当于POSIX的函数

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

最简单 :用“eval echo”代替“魔术”。

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

问题:你将遇到其他variables的问题,因为eval是邪恶的。 例如:

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

请注意,注射的问题在第一次扩展时不会发生。 所以如果你只是用eval echo来代替magic ,你应该没问题。 但是,如果你echo $(eval echo ~) ,这将是易受注射。

同样的,如果你使用eval echo ~而不是eval echo "~" ,那么这个计数就会被计算为两次扩展,因此可以马上注入。

Interesting Posts