如何在bash中手动扩展一个特殊变量(ex:~tilde)

mad*_*mha 120 bash scripting tilde tilde-expansion

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

~/a/b/c
Run Code Online (Sandbox Code Playgroud)

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

我是如何在不扩展的情况下将其传递到我的脚本中的?我用双引号传递了围绕它的论点.

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

ls -lt "~"
Run Code Online (Sandbox Code Playgroud)

这正是我所处的情况.我想要扩展代字号.换句话说,我应该用什么来代替魔法来使这两个命令相同:

ls -lt ~/abc/def/ghi
Run Code Online (Sandbox Code Playgroud)

ls -lt $(magic "~/abc/def/ghi")
Run Code Online (Sandbox Code Playgroud)

请注意〜/ abc/def/ghi可能存在也可能不存在.

Håk*_*and 99

如果变量var是由用户输入的,eval应该使用利用扩展波形符

eval var=$var  # Do not use this!
Run Code Online (Sandbox Code Playgroud)

原因是:用户可能会偶然(或按目的)键入类型,例如var="$(rm -rf $HOME/)"可能带来灾难性后果.

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

var="${var/#\~/$HOME}"
Run Code Online (Sandbox Code Playgroud)

  • 你怎么能改变~userName /而不只是〜/? (6认同)
  • 在`"$ {var /#\〜/ $ HOME}"`中`#`的目的是什么? (3认同)
  • @Jahid在[手册](http://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html)中对此进行了解释.它强制代字号仅在`$ var`的开头匹配. (3认同)
  • 请在您的答案中编辑“${var/#...}”是一种仅在开头匹配的特殊 bash 语法的解释。带网址。我已经使用 bash 几十年了,但不知道那个。此外,eval() 可能存在的问题是恶意代码注入。 (2认同)

wkl*_*wkl 87

由于StackOverflow的性质,我不能仅仅接受这个答案,但是在我发布这篇文章的5年间,我得到的答案远比我公认的基本和非常糟糕的答案要好得多(我很年轻,不要杀人)我).

该主题中的其他解决方案是更安全,更好的解决方案.我最好选择以下两种方法之一:


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

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

#!/bin/bash

homedir=~
eval homedir=$homedir
echo $homedir # prints home path
Run Code Online (Sandbox Code Playgroud)

或者,只要${HOME}您想要用户的主目录即可使用.

  • 我发现`$ {HOME}'最具吸引力.有没有理由不将此作为您的主要推荐?无论如何,谢谢! (31认同)
  • 使用'eval`是一个可怕的建议,它真的很糟糕,它得到了这么多的赞成.当变量的值包含shell元字符时,您将遇到各种问题. (11认同)
  • 当变量有空格时,你有解决方法吗? (3认同)
  • @sage不,我也更喜欢“${HOME}” - 但我的答案是解释如何用“~”来做这件事,就像问题所问的那样。 (2认同)

Cha*_*ffy 20

将自己从先前的答案中抹去,以便在没有与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%:}"
}
Run Code Online (Sandbox Code Playgroud)

......用作......

path=$(expandPath '~/hello')
Run Code Online (Sandbox Code Playgroud)

或者,一种更简单的方法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
}
Run Code Online (Sandbox Code Playgroud)

  • 看看你的代码看起来你正在使用大炮杀死一只蚊子.有**是一个更简单的方式.. (4认同)
  • @Gino,肯定是一种更简单的方式; 问题是,是否有一种更安全的简单方法. (2认同)
  • @Gino,...我_do_假设可以使用`printf%q`来逃避除了波浪号以外的所有内容,并且_then_使用`eval`而没有风险. (2认同)
  • 安全,是的,但是非常不完整。我的代码并不是很有趣,它很复杂,因为波浪号扩展所完成的实际操作很复杂。 (2认同)

gli*_*ond 10

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

$ echo "echo $var" | bash
Run Code Online (Sandbox Code Playgroud)

该命令的作用的解释:

  1. 通过...调用创建一个新的 bash 实例bash
  2. 获取字符串"echo $var"并替换$var为变量的值(因此替换后字符串将包含波形符);
  3. 获取步骤 2 生成的字符串并将其发送到步骤 1 中创建的 bash 实例,我们在这里通过使用字符调用echo和管道输出来完成此操作|

基本上,我们正在运行的当前 bash 实例取代了我们作为另一个 bash 实例的用户的位置,并为我们输入命令"echo ~..."


edd*_*eek 9

使用eval的一种安全方法是"$(printf "~/%q" "$dangerous_path")".请注意,这是特定于bash的.

#!/bin/bash

relativepath=a/b/c
eval homedir="$(printf "~/%q" "$relativepath")"
echo $homedir # prints home path
Run Code Online (Sandbox Code Playgroud)

有关详情,请参阅此问题

另外,请注意在zsh下这将是一样简单 echo ${~dangerous_path}


Jay*_*Jay 8

这个怎么样:

path=`realpath "$1"`
Run Code Online (Sandbox Code Playgroud)

要么:

path=`readlink -f "$1"`
Run Code Online (Sandbox Code Playgroud)

  • @dangonfast 如果您将波形符设置为引号,则这将不起作用,结果是 `&lt;workingdir&gt;/~`。 (5认同)

Noa*_*man 7

在birryree和halloleo的答案上扩展(没有双关语):一般方法是使用eval,但它带有一些重要的警告,即>变量中的空格和输出重定向().以下似乎对我有用:

mypath="$1"

if [ -e "`eval echo ${mypath//>}`" ]; then
    echo "FOUND $mypath"
else
    echo "$mypath NOT FOUND"
fi
Run Code Online (Sandbox Code Playgroud)

尝试使用以下每个参数:

'~'
'~/existing_file'
'~/existing file with spaces'
'~/nonexistant_file'
'~/nonexistant file with spaces'
'~/string containing > redirection'
'~/string containing > redirection > again and >> again'
Run Code Online (Sandbox Code Playgroud)

说明

  • ${mypath//>}带出来>的字符可能在揍一个文件eval.
  • eval echo ...是什么是实际的波浪线扩展
  • -e参数周围的双引号用于支持带空格的文件名.

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

  • 您可以考虑查看名称包含`$(rm -rf.)`的行为. (3认同)