我的bash脚本中有一个变量,其值如下:
~/a/b/c
请注意,它是未展开的代字号。当我对这个变量执行ls -lt(称之为$ VAR)时,我没有这样的目录。我想让bash解释/扩展这个变量而不执行它。换句话说,我希望bash运行eval但不运行evaluate命令。在bash中这可能吗?
如何在没有扩展的情况下将其传递到我的脚本中?我用双引号传递了围绕它的论点。
尝试此命令以查看我的意思:
ls -lt "~"
这正是我所处的情况。我想要扩展代字号。换句话说,我应该用什么来替换魔法才能使这两个命令相同:
ls -lt ~/abc/def/ghi
和
ls -lt $(magic "~/abc/def/ghi")
请注意〜/ abc / def / ghi可能存在也可能不存在。
答案 0 :(得分:93)
如果用户输入变量var
,eval
应使用 来扩展代字号
eval var=$var # Do not use this!
原因是:用户可能会偶然(或按目的)键入例如var="$(rm -rf $HOME/)"
,并可能造成灾难性后果。
更好(更安全)的方法是使用Bash参数扩展:
var="${var/#\~/$HOME}"
答案 1 :(得分:84)
由于StackOverflow的性质,我不能仅仅接受这个答案,但是自从我发布这个答案以来的5年间,答案远比我公认的基本和非常糟糕的答案(我是年轻,不要杀我。)
此主题中的其他解决方案是更安全,更好的解决方案。最好,我会选择这两个中的任何一个:
出于历史目的的原始答案(但请不要使用此内容)
如果我没弄错的话,"~"
将不会以这种方式被bash脚本扩展,因为它被视为文字字符串"~"
。您可以通过eval
强行扩展。
#!/bin/bash
homedir=~
eval homedir=$homedir
echo $homedir # prints home path
或者,如果您想要用户的主目录,只需使用${HOME}
。
答案 2 :(得分:18)
从a prior answer整理自己,以便在没有与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
}
答案 3 :(得分:8)
使用eval的安全方法是"$(printf "~/%q" "$dangerous_path")"
。请注意,这是特定于bash的。
#!/bin/bash
relativepath=a/b/c
eval homedir="$(printf "~/%q" "$relativepath")"
echo $homedir # prints home path
有关详细信息,请参阅this question
另外,请注意,在zsh下,这将像echo ${~dangerous_path}
答案 4 :(得分:7)
在birryree和halloleo的答案上扩展(没有双关语):一般方法是使用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
参数周围的双引号用于支持带空格的文件名。也许有一个更优雅的解决方案,但这是我能够想到的。
答案 5 :(得分:6)
这个怎么样:
path=`realpath "$1"`
或者:
path=`readlink -f "$1"`
答案 6 :(得分:3)
这是一个荒谬的解决方案:
$ echo "echo $var" | bash
解释这个命令的作用:
bash
;"echo $var"
并将 $var
替换为变量的值(因此替换后字符串将包含波浪号);echo
并使用 |
字符传输其输出来完成此操作。基本上,我们正在运行的当前 bash 实例代替我们作为另一个 bash 实例的用户并为我们键入命令 "echo ~..."
。
答案 7 :(得分:2)
我相信这就是你要找的东西
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
答案 8 :(得分:1)
这是我的解决方案:
byte MAX = (byte) 255;
byte[] r = new byte[] {MAX, 127, 0};
byte[] g = new byte[] {MAX, 127, 0};
byte[] b = new byte[] {MAX, 127, 0};
IndexColorModel indexColorModel = new IndexColorModel(2, 3, r, g, b);
//6 -> bits per pixel, 3 -> size of the color arrays
BufferedImage copy = new BufferedImage(width, height,
BufferedImage.TYPE_BYTE_INDEXED, indexColorModel);
答案 9 :(得分:1)
正确使用eval
:验证。
case $1${1%%/*} in
([!~]*|"$1"?*[!-+_.[:alnum:]]*|"") ! :;;
(*/*) set "${1%%/*}" "${1#*/}" ;;
(*) set "$1"
esac&& eval "printf '%s\n' $1${2+/\"\$2\"}"
答案 10 :(得分:1)
这是POSIX功能相当于HåkonHægland的Bash answer
expand_tilde() {
tilde_less="${1#\~/}"
[ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less"
printf '%s' "$tilde_less"
}
2017-12-10编辑:在评论中为@CharlesDuffy添加'%s'
。
答案 11 :(得分:1)
为什么不直接使用getent获取用户的主目录?
$ getent passwd mike | cut -d: -f6
/users/mike
答案 12 :(得分:0)
仅扩展birryree对包含空格的路径的回答:您不能使用eval
命令,因为它按空格分隔评估。一种解决方案是临时替换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_"
。
答案 13 :(得分:0)
你可能会发现在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
答案 14 :(得分:0)
最简单:将'magic'替换为'eval echo'。
$ eval echo "~"
/whatever/the/f/the/home/directory/is
问题:你会遇到其他变量的问题,因为eval是邪恶的。例如:
$ # 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 "~"
,则会计算两次展开,因此可以立即进行注射。
答案 15 :(得分:0)
在使用read -e读取路径后,我已经用可变参数替换完成了此操作。因此,用户可以使用制表符补全路径,如果用户输入〜路径,则会对其进行排序。
read -rep "Enter a path: " -i "${testpath}" testpath
testpath="${testpath/#~/${HOME}}"
ls -al "${testpath}"
附加的好处是,如果没有代字号,则变量不会发生任何变化;如果有代字号但不在第一个位置,则也将被忽略。
(我包括-i以便读取,因为我在循环中使用了它,因此如果出现问题,用户可以修复路径。)