如何在bash中手动扩展特殊变量(例如:〜tilde)
-
09-10-2019 - |
题
我的bash脚本中有一个变量,其价值就是这样:
~/a/b/c
请注意,它是未来的Tilde。当我在此变量上执行LS -LT(称为$ var)时,我没有这样的目录。我想让Bash解释/扩展此变量而不执行该变量。换句话说,我希望BASH运行评估,但不运行评估命令。这可以在狂欢中吗?
我如何在没有扩展的情况下将其传递到我的脚本中?我通过双引号传递了围绕它的论点。
尝试此命令以查看我的意思:
ls -lt "~"
这正是我所处的情况。我希望tilde扩大。换句话说,我应该用什么代替魔术,以使这两个命令相同:
ls -lt ~/abc/def/ghi
和
ls -lt $(magic "~/abc/def/ghi")
请注意,〜/abc/def/ghi可能存在也可能不存在。
解决方案
由于stackoverflow的性质,我不能仅仅让这个答案不被接受,但是在我发布此答案以来的5年中,答案比我公认的基本和相当糟糕的答案更好(我还很年轻,不要杀人我)。
该线程中的其他解决方案是更安全,更好的解决方案。最好是,我会选择这两个中的任何一个:
出于历史目的的原始答案(但请不要使用)
如果我没错的话, "~"
不会以这种方式来扩展bash脚本,因为它被视为字符串 "~"
. 。您可以通过 eval
像这样。
#!/bin/bash
homedir=~
eval homedir=$homedir
echo $homedir # prints home path
或者,只需使用 ${HOME}
如果您想要用户的主目录。
其他提示
如果变量 var
是用户输入的 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 -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%:}"
}
...用作...
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
}
一种安全使用评估的方法是 "$(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"`
扩展Birryree和Haloleo的答案(不打算双关语):一般方法是使用 eval
, ,但它带有一些重要的警告,即空间和输出重定向(>
)在变量中。以下似乎对我有用:
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
论点是为了支持带有空间的文件名。
也许有一个更优雅的解决方案,但这就是我能想到的。
我相信这就是您想要的
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
这是我的解决方案:
#!/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
正确:通过验证。
case $1${1%%/*} in
([!~]*|"$1"?*[!-+_.[:alnum:]]*|"") ! :;;
(*/*) set "${1%%/*}" "${1#*/}" ;;
(*) set "$1"
esac&& eval "printf '%s\n' $1${2+/\"\$2\"}"
这是HåkonHægland'sBash的POSIX功能 回答
expand_tilde() {
tilde_less="${1#\~/}"
[ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less"
printf '%s' "$tilde_less"
}
2017-12-10编辑:添加 '%s'
评论中的@charlesduffy。
只是为了扩展 Birryree有空间的路径的答案:您不能使用 eval
命令是因为它将评估分开。一种解决方案是临时替换空格以替换命令:
mypath="~/a/b/c/Something With Spaces"
expandedpath=${mypath// /_spc_} # replace spaces
eval expandedpath=${expandedpath} # put spaces back
expandedpath=${expandedpath//_spc_/ }
echo "$expandedpath" # prints e.g. /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)作为实用程序 - 将其保存为 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
最简单: :用“ eval echo”替换“魔术”。
$ eval echo "~"
/whatever/the/f/the/home/directory/is
问题: 您将遇到其他变量问题,因为评估是邪恶的。例如:
$ # home is /Users/Hacker$(s)
$ s="echo SCARY COMMAND"
$ eval echo $(eval echo "~")
/Users/HackerSCARY COMMAND
请注意,注射问题不会在第一次扩展中发生。因此,如果您简单地替换 magic
和 eval echo
, ,你应该没事。但是如果你这样做 echo $(eval echo ~)
, ,那将容易注射。
同样,如果你这样做 eval echo ~
代替 eval echo "~"
, ,这将算作两次扩展,因此可以立即进行注射。
我使用read -e(以及其他)在路径上读取路径后进行了可变参数替换。因此,用户可以将路径列入路径,如果用户输入〜路径,则可以对路径进行排序。
read -rep "Enter a path: " -i "${testpath}" testpath
testpath="${testpath/#~/${HOME}}"
ls -al "${testpath}"
额外的好处是,如果变量没有任何tilde,那么如果有tilde,但在第一个位置没有tilde,它也被忽略了。
(我将读取的-i包含在循环中,因为我在循环中使用此信息,因此如果存在问题,用户可以修复路径。)