如何在 bash 中手动扩展特殊变量(例如:~波浪号)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3963716/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to manually expand a special variable (ex: ~ tilde) in bash
提问by madiyaan damha
I have a variable in my bash script whose value is something like this:
我的 bash 脚本中有一个变量,它的值是这样的:
~/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?
请注意,它是未扩展的波浪号。当我对这个变量执行 ls -lt 时(称之为 $VAR),我没有得到这样的目录。我想让 bash 解释/扩展这个变量而不执行它。换句话说,我希望 bash 运行 eval 但不运行评估的命令。这在 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:
这正是我所处的情况。我想要扩展波浪号。换句话说,我应该用什么替换 magic 以使这两个命令相同:
ls -lt ~/abc/def/ghi
and
和
ls -lt $(magic "~/abc/def/ghi")
Note that ~/abc/def/ghi may or may not exist.
请注意 ~/abc/def/ghi 可能存在也可能不存在。
采纳答案by wkl
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).
由于 StackOverflow 的性质,我不能只是让这个答案不被接受,但是在我发布这篇文章后的 5 年里,有比我公认的基本和非常糟糕的答案更好的答案(我还年轻,不要杀了我)。
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.
如果我没记错的话,"~"
bash 脚本不会以这种方式扩展,因为它被视为文字 string "~"
。您可以通过eval
这样强制扩展。
#!/bin/bash
homedir=~
eval homedir=$homedir
echo $homedir # prints home path
Alternatively, just use ${HOME}
if you want the user's home directory.
或者,${HOME}
如果您想要用户的主目录,只需使用。
回答by H?kon H?gland
If the variable var
is input by the user, eval
should notbe used to expand the tilde using
如果变量var
是由用户输入的,eval
应该不使用利用扩展波形符
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.
原因是:用户可能会意外(或故意)打字,例如var="$(rm -rf $HOME/)"
可能造成灾难性后果。
A better (and safer) way is to use Bash parameter expansion:
更好(更安全)的方法是使用 Bash 参数扩展:
var="${var/#\~/$HOME}"
回答by Charles Duffy
Plagarizing myself from a prior answer, to do this robustly without the security risks associated with eval
:
从先前的答案中剽窃自己,以稳健地做到这一点,而不会带来与eval
以下相关的安全风险:
expandPath() {
local path
local -a pathElements resultPathElements
IFS=':' read -r -a pathElements <<<""
: "${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:
或者,一种更简单的方法,eval
谨慎使用:
expandPath() {
case 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' ""
;;
esac
}
回答by eddygeek
A safe way to use eval is "$(printf "~/%q" "$dangerous_path")"
. Note that is bash specific.
使用 eval 的安全方法是"$(printf "~/%q" "$dangerous_path")"
. 请注意,这是特定于 bash 的。
#!/bin/bash
relativepath=a/b/c
eval homedir="$(printf "~/%q" "$relativepath")"
echo $homedir # prints home path
See this questionfor details
见这个问题的详细信息
Also, note that under zsh this would be as as simple as echo ${~dangerous_path}
另外,请注意,在 zsh 下,这就像 echo ${~dangerous_path}
回答by Jay
How about this:
这个怎么样:
path=`realpath ""`
Or:
或者:
path=`readlink -f ""`
回答by Noach Magedman
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:
在 birryree 和 Halloleo 的答案上扩展(无双关语):一般方法是使用eval
,但它带有一些重要的警告,即>
变量中的空格和输出重定向 ( )。以下似乎对我有用:
mypath=""
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 theeval
. - The
eval echo ...
is what does the actual tilde expansion - The double-quotes around the
-e
argument are for support of filenames with spaces.
- 在
${mypath//>}
剔除>
这可能在揍一个文件中的字符eval
。 - 该
eval echo ...
是什么是实际的波浪线扩展 -e
参数周围的双引号是为了支持带空格的文件名。
Perhaps there's a more elegant solution, but this is what I was able to come up with.
也许有一个更优雅的解决方案,但这是我能够想出的。
回答by Orwellophile
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" ""
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
回答by Gino
Here's my solution:
这是我的解决方案:
#!/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 "")
echo "Result = $result"
回答by Paul M
why not delve straight into getting the user's home directory with getent?
为什么不直接深入研究使用 getent 获取用户的主目录?
$ getent passwd mike | cut -d: -f6
/users/mike
回答by mikeserv
Just use eval
correctly: with validation.
eval
正确使用:带验证。
case ${1%%/*} in
([!~]*|""?*[!-+_.[:alnum:]]*|"") ! :;;
(*/*) set "${1%%/*}" "${1#*/}" ;;
(*) set ""
esac&& eval "printf '%s\n' ${2+/\"$2\"}"