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

时间:2010-10-18 21:54:23

标签: bash scripting tilde tilde-expansion

我的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可能存在也可能不存在。

16 个答案:

答案 0 :(得分:93)

如果用户输入变量vareval应使用 来扩展代字号

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

解释这个命令的作用:

  1. 创建一个新的 bash 实例,通过...调用 bash;
  2. 取字符串 "echo $var" 并将 $var 替换为变量的值(因此替换后字符串将包含波浪号);
  3. 将第 2 步产生的字符串发送到第 1 步中创建的 bash 实例,我们在这里通过调用 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以便读取,因为我在循环中使用了它,因此如果出现问题,用户可以修复路径。)