170

我的 bash 脚本中有一个变量,其值如下所示:

~/a/b/c

请注意,它是未扩展的波浪号。当我对这个变量执行 ls -lt(称为 $VAR)时,我没有得到这样的目录。我想让 bash 解释/扩展这个变量而不执行它。换句话说,我希望 bash 运行 eval 但不运行评估的命令。这在bash中可能吗?

我是如何设法在不扩展的情况下将其传递到我的脚本中的?我通过用双引号括起来的参数。

试试这个命令看看我的意思:

ls -lt "~"

这正是我所处的情况。我希望扩大波浪号。换句话说,我应该用什么替换魔法以使这两个命令相同:

ls -lt ~/abc/def/ghi

ls -lt $(magic "~/abc/def/ghi")

请注意,~/abc/def/ghi 可能存在也可能不存在。

4

16 回答 16

146

如果变量var由用户输入,则eval不应使用扩展波浪号使用

eval var=$var  # Do not use this!

原因是:用户可能会意外(或故意)键入例如var="$(rm -rf $HOME/)"可能造成灾难性后果的类型。

更好(更安全)的方法是使用 Bash 参数扩展:

var="${var/#\~/$HOME}"
于 2014-12-15T13:26:18.433 回答
105

由于 StackOverflow 的性质,我不能仅仅让这个答案不被接受,但是在我发布这个答案之后的 5 年里,有比我公认的初级和非常糟糕的答案更好的答案(我还年轻,不要杀我)。

该线程中的其他解决方案是更安全、更好的解决方案。最好,我会选择以下两者中的任何一个:


用于历史目的的原始答案(但请不要使用它)

如果我没记错的话,"~"bash 脚本不会以这种方式扩展它,因为它被视为文字字符串"~"eval您可以通过这样的方式强制扩展。

#!/bin/bash

homedir=~
eval homedir=$homedir
echo $homedir # prints home path

或者,${HOME}如果您想要用户的主目录,只需使用。

于 2010-10-18T21:59:37.100 回答
27

从先前的答案中剽窃自己,在没有与以下相关的安全风险的情况下稳健地做到这一点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
}
于 2015-03-27T21:26:57.800 回答
11

这个怎么样:

path=`realpath "$1"`

或者:

path=`readlink -f "$1"`
于 2010-10-18T22:31:35.660 回答
10

使用 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}

于 2013-02-28T18:04:46.473 回答
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用于支持带空格的文件名。

也许有一个更优雅的解决方案,但这是我能够想出的。

于 2012-08-14T09:56:34.273 回答
6

这是一个荒谬的解决方案:

$ echo "echo $var" | bash

该命令作用的解释:

  1. 创建一个新的 bash 实例,通过...调用bash
  2. 取字符串"echo $var"$var用变量的值替换(因此在替换后字符串将包含波浪号);
  3. take the string produced by step 2 and send it to the instance of bash created in step one, which we do here by calling echo and piping its output with the | character.

Basically the current bash instance we're running takes our place as the user of another bash instance and types in the command "echo ~..." for us.

于 2021-02-13T01:39:35.010 回答
3

为什么不直接深入研究使用 getent 获取用户的主目录呢?

$ getent passwd mike | cut -d: -f6
/users/mike
于 2020-01-08T18:01:00.237 回答
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
于 2015-06-11T01:32:02.240 回答
2

这是相当于 Håkon Hægland 的 Bash答案的 POSIX 函数

expand_tilde() {
    tilde_less="${1#\~/}"
    [ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less"
    printf '%s' "$tilde_less"
}

2017-12-10编辑:'%s'在评论中添加@CharlesDuffy。

于 2016-08-25T19:06:20.037 回答
1

这是我的解决方案:

#!/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"
于 2015-08-21T14:08:16.287 回答
1

最简单的:用“eval echo”替换“magic”。

$ 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

请注意,注入问题不会在第一次扩展时发生。所以如果你只是用 替换magiceval echo你应该没问题。但如果你这样做echo $(eval echo ~),那将很容易被注射。

同样,如果你不这样做eval echo ~eval echo "~"那将被视为两次扩展,因此可以立即进行注入。

于 2017-01-25T01:15:02.753 回答
0

只是为了扩展biryree对带有空格的路径的答案:您不能按原样使用该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_"

于 2011-12-26T04:36:07.750 回答
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
于 2015-10-28T14:39:05.170 回答
0

只要eval正确使用:有验证。

case $1${1%%/*} in
([!~]*|"$1"?*[!-+_.[:alnum:]]*|"") ! :;;
(*/*)  set "${1%%/*}" "${1#*/}"       ;;
(*)    set "$1" 
esac&& eval "printf '%s\n' $1${2+/\"\$2\"}"
于 2015-12-13T04:49:57.747 回答
0

在使用 read -e (以及其他)读取路径后,我已经通过变量参数替换完成了此操作。因此,用户可以使用制表符完成路径,如果用户输入 ~ 路径,它就会被排序。

read -rep "Enter a path:  " -i "${testpath}" testpath 
testpath="${testpath/#~/${HOME}}" 
ls -al "${testpath}" 

额外的好处是,如果没有波浪号,则变量不会发生任何事情,如果有波浪号但不在第一个位置,它也会被忽略。

(我包括 -i 用于读取,因为我在循环中使用它,因此如果出现问题,用户可以修复路径。)

于 2019-06-19T10:04:36.307 回答