如何在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 "~"
,那么这个计数就会被计算为两次扩展,因此可以马上注入。