我的bash脚本中有一个变量,其值如下所示:
请注意,它是未扩展的波形符号。 当我对这个变量执行ls -lt(称之为$ VAR)时,我没有这样的目录。 我想让bash解释/扩展这个变量而不执行它。 换句话说,我希望bash运行eval但不运行evaluate命令。 在bash中这可能吗?
我是如何在不扩展的情况下将其传递到我的脚本中的? 我用双引号传递了围绕它的论点。
试试这个命令看看我的意思:
这正是我所处的情况。我想要扩展代字号。 换句话说,我应该用什么来代替魔法来使这两个命令相同:
和
1
| ls -lt $(magic"~/abc/def/ghi") |
请注意?/ abc / def / ghi可能存在也可能不存在。
-
您可能会发现引号中的Tilde扩展也很有用。 它主要但不完全避免使用eval。
-
您的变量是如何分配未展开的波浪号的? 也许所有需要的是使用波形符号在引号外分配该变量。 foo=~/"$filepath"或foo="$HOME/$filepath"
如果用户输入变量var,则不应使用eval来扩展波形符
1
| eval var=$var # Do not use this! |
原因是:用户可能偶然(或按目的)键入例如var="$(rm -rf $HOME/)",可能带来灾难性后果。
更好(更安全)的方法是使用Bash参数扩展:
-
你怎么能改变~userName /而不只是?/?
-
@aspergillusOryzae好问题。解决方法如下:stackoverflow.com/a/2069835/2173773
-
"${var/#\~/$HOME}"中#的目的是什么?
-
@Jahid手册中对此进行了解释。它强制波浪号仅在$var的开头匹配。
-
谢谢。 (1)为什么我们需要\~即逃避~? (2)您的回复假定~是$var中的第一个字符。我们如何忽略$var中的前导空格?
-
@Tim感谢您的评论。是的,你是对的,我们不需要转义代字号,除非它是不带引号的字符串的第一个字符,或者它是在未加引号的字符串中跟随:。更多信息在文档中。若要删除前导空格,请参阅如何从Bash变量中修剪空格?
-
请在答案中编辑"$ {var /#...}"是一种特殊的bash语法,只能在开头匹配。使用URL。几十年来我一直使用bash但不知道那个。此外,eval()可能存在的问题是恶意代码注入。
由于StackOverflow的性质,我不能仅仅接受这个答案,但是在我发布这篇文章的5年间,我得到的答案要远远超过我公认的基本和非常糟糕的答案(我很年轻,不要杀人)我)。
该主题中的其他解决方案是更安全,更好的解决方案。我最好选择以下两种方法之一:
-
查理的达菲的解决方案
-
H?kon H?gland的解决方案
出于历史目的的原始答案(但请不要使用此)
如果我没弄错的话,"~"将不会以这种方式被bash脚本扩展,因为它被视为文字字符串"~"。你可以像这样通过eval强制扩展。
1 2 3 4 5
| #!/bin/bash
homedir=~
eval homedir=$homedir
echo $homedir # prints home path |
或者,如果您想要用户的主目录,只需使用${HOME}。
-
当变量有空格时,你有解决方法吗?
-
@Hugo - 你是说像VAR="Something With Spaces"?
-
这里解释了?未扩展的原因:fvue.nl/wiki/Bash:_Why_use_eval_with_variable_expansion%3F
-
我发现${HOME}最吸引人。有没有理由不将此作为您的主要推荐?无论如何,谢谢!
-
@sage不,我也更喜欢${HOME} - 但我的回答是解释如何用~来解决这个问题。
-
+1 - 我需要扩展?$ some_other_user并且当$ HOME无法工作时eval工作正常,因为我不需要当前用户回家。
-
@birryree是的,就是这样。更具体地说是具有空格的路径:'~/some very nice dir/that has spaces'
-
使用eval是一个可怕的建议,它真的很糟糕,它得到了这么多的赞成。当变量的值包含shell元字符时,您将遇到各种问题。
-
@ user2719058同意。请改用Bash参数扩展。
-
@olivecoder,还有其他方法可以做到这一点(在其他答案中给出!),当你??尝试expandPath '/tmp/$(rm -rf /)'时不会擦除你的机器。安全隐患是存在的。
-
@CharlesDuffy由于SO的性质,我不能删除我的答案,也不能让它不被接受,所以我能做的最好就是将未来的读者链接到这个线程中更好/更安全的解决方案。
-
@CharlesDuffy:哇!那是五年前的......好吧,我不确定为什么我被特别推荐但是我们走了:首先是上下文:我当时正在寻找一种方法来找到一个主目录用户在新创建的流浪汉机器中作为参数传递。
-
@olivecoder,......这是一个非常合理的用例,当然$HOME对你不起作用肯定是有道理的,但有更多选择,而不是选择(1)危险地使用eval或(2) )仅替换$HOME。例如,参见Orwellophile的最新答案,安全使用eval。只要用户名本身可信,Gino的最新编辑也是安全的。或者说,自3月以来我已经得到了答案 - 冗长而笨拙,但根本没有使用eval(以及相关的安全风险)。
-
当时我无法完成我的评论,然后,我不允许稍后编辑它。所以我很感激(再次感谢@birryree)这个解决方案,因为它在当时的特定环境中有所帮助。谢谢Charles让我知道。
-
感谢您光荣地将免责声明放在首位。绝对不值得为此下降! ;-)
从先前的答案中整理自己,以便在没有与eval相关的安全风险的情况下做到这一点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 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
'"${result%:}"
} |
......用作......
1
| path=$(expandPath '~/hello') |
或者,一个更简单的方法,小心使用eval:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| expandPath() {
case $1 in
~[+-]*)
local content content_q
printf -v content_q '%q'"${1:2}"
eval"content=${1:0:2}${content_q}"
printf '%s
'"$content"
;;
~*)
local content content_q
printf -v content_q '%q'"${1:1}"
eval"content=~${content_q}"
printf '%s
'"$content"
;;
*)
printf '%s
'"$1"
;;
esac
} |
-
看看你的代码看起来你正在使用大炮杀死一只蚊子。必须有一个更简单的方式..
-
@Gino,肯定是一种更简单的方式;问题是,是否有一种更安全的简单方法。
-
@Gino,...我认为可以使用printf %q来逃避除了波浪号以外的所有内容,然后使用eval而不会有风险。
-
@Gino,......等实现。
-
我刚刚发布了这个问题的解决方案。它使用正则表达式来扩展波形符,并且应该相当安全。
-
安全,是的,但非常不完整。我的代码并不复杂,因为它的乐趣 - 它很复杂,因为波浪扩展所做的实际操作很复杂。
-
@Gino - 你不需要正则表达式。有一个命令。 pathchk。
-
@mikeserv,我不确定pathchk是否适用于此。任何给定系统上的便携式设备通常比有效设备大得多(意味着您经常排除本地文件系统允许的路径),并且有效设置不保证eval -safe。
-
@CharlesDuffy - 也许这是真的 - 但你能提供一些例子吗?
-
@mikeserv,当然??。 touch '/tmp/$(hello)'在ext2衍生物上工作 - 它是有效的,但它包含的字符不是POSIX定义的可移植集的一部分,而pathchk将声明它无效。
-
@CharlesDuffy - 是的,它将与-pP,但这就是重点。我不是在寻找有效的路径名 - 而是有效的用户名。
-
@mikeserv,问题是,如果不是使用pathchk过滤你使用printf %q来逃避pre-eval - 正如Orwellophile所做的那样 - 那么你就可以获得两全其美:你可以工作即使它们超出POSIX规范,也可以使用所有本地有效的名称。为什么限制自己呢?
-
...虽然是,但如果用户名的POSIX规范允许使用便携式文件集中的所有字符,则pathchk非常适合。
-
@CharlesDuffy - 但是你也会锁定自己使用像bash这样糟糕的shell。更简单的只是使用工具,因为我们看到它使用规格。有些人不认为bash是如此可怕的外壳 - 我认为人们有点迷惑,但后来也许我有点迷惑。我喜欢快速按照规范做事,所以不论一个人的妄想,他们都可以工作。
-
我同意bash是糟糕的,但ksh93并不可怕,它也提供%q。 :)
-
@CharlesDuffy - ^^^^ !!!! - 顺便说一下 - 你看到我的更新是ksh死了吗?题?
-
好吧,ksh93不是一个POSIX-y shell。我认为要成为一种真正不可怕的编程语言,需要成为一名LISP。 :)
-
@CharlesDuffy - 我们至少可以同意ksh。不过,我想我们都可以对lisp大笑。无论如何,看看这里的更新 - 如果你还没有。
-
特殊~-和~+案例的目的是什么?我以前没见过这些。
-
它们包含在man bash - ~+中扩展到当前目录(与$PWD相同),~-扩展为$OLDPWD,即cd -将移动到的目录。
-
您仍然错过了几个案例(在第一个程序中):仅~,~+或~-(即没有目录分隔符)。最简单的修复可能是写case"$path/",并从${path#}替换中删除/。我认为这也可以简化~name案例。
-
@CharlesDuffy在IFS=: read _ _ _ _ _ homedir _ < <(getent passwd"$username")行中,为什么不使用read -r来防止反斜杠的变形?
-
@leetbacoon,...在编写代码时,我的印象是,当一个值包含一个文字冒号时,会使用反斜杠来转义它,使read的行为完全不是我们想要的-r。但是,阅读规范时,我发现这种印象是错误的 - 冒号只是内容不允许 - 所以-r是合适的。
使用eval的安全方法是"$(printf"~/%q""$dangerous_path")"。请注意,这是特定于bash的。
1 2 3 4 5
| #!/bin/bash
relativepath=a/b/c
eval homedir="$(printf"~/%q""$relativepath")"
echo $homedir # prints home path |
有关详情,请参阅此问题
另请注意,在zsh下,这将像echo ${~dangerous_path}一样简单
-
echo ${~root}在zsh上没有输出(mac os x)
-
export test="~root/a b"; echo ${~test}
在birryree和halloleo的答案上扩展(没有双关语):一般方法是使用eval,但它带有一些重要的警告,即变量中的空格和输出重定向(>)。以下似乎对我有用:
1 2 3 4 5 6 7
| mypath="$1"
if [ -e"`eval echo ${mypath//>}`" ]; then
echo"FOUND $mypath"
else
echo"$mypath NOT FOUND"
fi |
尝试使用以下每个参数:
1 2 3 4 5 6 7
| '~'
'~/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参数周围的双引号用于支持带空格的文件名。
也许有一个更优雅的解决方案,但这是我能够想到的。
-
您可以考虑查看名称中包含$(rm -rf .)的行为。
-
但是,这不会在实际包含>字符的路径上中断吗?
这个怎么样:
要么:
-
看起来不错,但我的mac上不存在realpath。你必须写path = $(realpath"$ 1")
-
嗨@Hugo。您可以在C中编译自己的realpath命令。例如,您可以使用命令行中的bash和gcc生成可执行文件realpath.exe:gcc -o realpath.exe -x c - <<< $'#include
int main(int c,char**v){char p[9999]; realpath(v[1],p); puts(p);}'。干杯
-
@Quuxplusone不是真的,至少在linux上:realpath ~ - > /home/myhome
我相信这就是你要找的东西
1 2 3 4 5 6
| 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.$$
} |
用法示例:
1 2 3 4 5
| $ magic ~nobody/would/look/here
/var/empty/would/look/here
$ magic ~invalid/this/will/not/expand
~invalid/this/will/not/expand |
-
令我感到惊讶的是printf %q并没有逃脱领先的倾斜 - 这几乎是诱人的将其视为一个错误,因为它是一种在其声明的目的上失败的情况。但是,在此期间,一个很好的电话!
-
实际上 - 这个错误在3.2.57和4.3.18之间的某个时刻被修复,因此这段代码不再有效。
-
好点,我已调整为代码删除领先如果它存在,所以所有修复和工作:)我测试没有引用参数,所以它在扩展之前调用函数。
-
在我看来很好。
这里的POSIX函数相当于H?kon H?gland的Bash答案
1 2 3 4 5
| expand_tilde() {
tilde_less="${1#\~/}"
["$1" !="$tilde_less" ] && tilde_less="$HOME/$tilde_less"
printf '%s'"$tilde_less"
} |
2017-12-10编辑:在评论中为@CharlesDuffy添加'%s'。
-
printf '%s
'"$tilde_less",也许?否则,如果要扩展的文件名包含反斜杠,%s或对printf有意义的其他语法,则会出错。除此之外,这是一个很好的答案 - 正确(当bash / ksh扩展不需要覆盖时),显然是安全的(没有与eval混淆)和简洁。
只需正确使用eval:验证。
1 2 3 4 5 6
| case $1${1%%/*} in
([!~]*|"$1"?*[!-+_.[:alnum:]]*|"") ! :;;
(*/*) set"${1%%/*}""${1#*/}" ;;
(*) set"$1"
esac&& eval"printf '%s
' $1${2+/"\$2"}" |
-
这可能是安全的 - 我没有找到它失败的情况。也就是说,如果我们要说"正确地"使用eval,我认为Orwellophile的答案遵循更好的做法:我相信shell的printf %q安全地逃避事情,而不是相信手写的验证代码没有错误。
-
@Charles Duffy - 这太傻了。 shell可能没有%q - 而printf是$PATH'd命令。
-
这个问题不是标记为bash吗?如果是这样,printf是内置的,并且保证%q存在。
-
@Charles Duffy - 什么版本?
-
2.02,我相信 - 因为bash首先得到了ksh的printf内置的克隆。
-
@Charles Duffy - 那是......很早。但是我仍然觉得很奇怪,你会比你在眼前编码更加信任,我已经足够使用bash之前知道不要相信它了。尝试:x=$(printf \\1); [ -n"$x" ] || echo but its not null!
-
让我们在聊天中继续讨论。
这是我的解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #!/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" |
-
此外,依赖echo意味着expandTilde -n不会按预期运行,并且POSIX未定义包含反斜杠的文件名的行为。请参见pubs.opengroup.org/onlinepubs/009604599/utilities/echo.html
-
接得好。我通常使用一台用户的机器,所以我没想到要处理这种情况。但我认为通过为其他用户翻阅/ etc / passwd文件,可以很容易地增强该函数以处理这种情况。我会把它作为别人的练习:)。
-
我已经在你认为过于复杂的答案中完成了这个练习(并处理了OLDPWD案例和其他案例)。 :)
-
实际上,我刚刚发现了一个相当简单的单线解决方案,应该处理其他用户案例:path = $(eval echo $ orgPath)
-
Dude,在参考1行语句时使用"抄袭"有点夸大其词,你不觉得吗?无论如何,我认为shell脚本编写者可以完全控制expandTilde()函数的调用了解它的局限性。仅仅因为声明是危险的并不意味着应该避免使用它。例如:'rm'是危险的,因为你可以用它消灭文件系统,但这并不意味着你不应该使用它。相反,必须熟悉一个人使用的工具的限制。
-
仅供参考:我刚刚更新了我的解决方案,以便它现在可以正确处理?用户名。而且,它也应该是相当安全的。即使你输入'/ tmp / $(rm -rf / *)'作为参数,它也应该优雅地处理它。
-
我认为在这一点上,有足够的人可以接受它并进一步改进它。感谢Charles Duffy指出?用户名和'/ tmp / $ {rm -rf /)'案例..
-
继续进行改变。我建议你在一个单独的帖子中进行,只是在你编辑我的帖子时引用我的帖子。
-
我实际上并不清楚需要做出的确切变化,这就是为什么我建议单独发帖。如果你告诉我差异,我会很乐意将它们纳入我的帖子。只是一行更改为path = $(eval"echo ..)?
-
嗯..我只是尝试在脚本中使用path = $(eval"echo'$ {BASH_REMATCH [1]}'"),这使得它甚至不再适用于基本情况'?'。
-
哦。对。是的,如果它逃脱了波浪形,它将无法工作。因为只有在IFS设置为非默认值时才有意义,所以实际上可能更容易确保永远不会发生。 path=$(unset IFS; eval"echo ${BASH_REMATCH[1]}")
-
(具体来说,我在这里担心的错误是包含用户名中存在的字符的IFS,导致扩展在后者的任何一侧被分成多个单词;如果未设置IFS,则默认为制表符,换行符和空格,你的正则表达式不允许任何这些)。
-
查尔斯,如果你能把它作为一个起点,我建议你做一个单独的帖子,然后引用我的。
-
我对现有的答案感到满意,因为它具有足够强大的功能;并且这个也是相当不错的形状:非默认的IFS错误是一个非常小的错误,它只会导致不正确的结果,而不是安全漏洞。我已经尝试清理我的评论历史中不再相关的部分内容。
在使用read -e(以及其他)读取路径后,我使用变量参数替换完成了此操作。 因此,用户可以选项卡完成路径,如果用户输入?路径,则会对其进行排序。
1 2 3
| read -rep"Enter a path: " -i"${testpath}" testpath
testpath="${testpath/#~/${HOME}}"
ls -al"${testpath}" |
额外的好处是,如果没有波形,变量没有任何变化,如果有波浪但不在第一个位置,它也会被忽略。
(因为我在循环中使用它所以包含-i用于读取,因此如果出现问题,用户可以修复路径。)
最简单:用'eval echo'代替'magic'。
1 2
| $ eval echo"~"
/whatever/the/f/the/home/directory/is |
问题:你会遇到其他变量的问题,因为eval是邪恶的。例如:
1 2 3 4
| $ # 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"~",那将被视为两倍扩展,因此可以立即注入。
-
与你所说的相反,这段代码是不安全的。例如,test s='echo; EVIL_COMMAND'。 (它会失败,因为您的计算机上不存在EVIL_COMMAND。但是如果该命令例如rm -r ~,它将删除您的主目录。)
您可能会发现在python中更容易做到这一点。
(1)从unix命令行:
1
| python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' ~/fred |
结果是:
(2)在bash脚本中作为一次性 - 将其保存为test.sh:
1 2 3 4 5
| #!/usr/bin/env bash
thepath=$(python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' $1)
echo $thepath |
运行bash ./test.sh会导致:
(3)作为一个实用程序 - 在路径的某个地方保存为expanduser,具有执行权限:
1 2 3 4 5 6
| #!/usr/bin/env python
import sys
import os
print os.path.expanduser(sys.argv[1]) |
然后可以在命令行上使用它:
或者在脚本中:
1 2 3 4 5
| #!/usr/bin/env bash
thepath=$(expanduser $1)
echo $thepath |
-
或者如何只将'?'传递给Python,返回"/ home / fred"?
-
需要莫尔报价。 echo $thepath是马车;需要echo"$thepath"来修复不太常见的情况(带有制表符或空格的名称被转换为单个空格的名称;带有展开它们的globs的名称),或printf '%s
'"$thepath"来修复不常见的情况(即文件)在XSI兼容系统上命名为-n或带反斜杠文字的文件。同样,thepath=$(expanduser"$1")
-
...要了解我对反斜杠文字的含义,请参阅pubs.opengroup.org/onlinepubs/009604599/utilities/echo.html - 如果任何参数包含反斜杠,POSIX允许echo以完全实现定义的方式运行; POSIX的可选XSI扩展要求默认(不需要-e或-e)这些名称的扩展行为。
只是为了扩展birryree对带空格的路径的答案:你不能按原样使用eval命令,因为它用空格分隔评估。一种解决方案是临时替换eval命令的空格:
1 2 3 4 5 6
| 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_"。
-
不适用于IFS中的制表符,换行符或其他任何内容...并且不提供包含$(rm -rf .)的路径等元字符的安全性