如何在 bash 中手动扩展特殊变量(例如:~波浪号)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3963716/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 19:46:48  来源:igfitidea点击:

How to manually expand a special variable (ex: ~ tilde) in bash

bashscriptingtildetilde-expansion

提问by madiyaan damha

I have a variable in my bash script whose value is something like this:

我的 bash 脚本中有一个变量,它的值是这样的:

~/a/b/c

Note that it is unexpanded tilde. When I do ls -lt on this variable (call it $VAR), I get no such directory. I want to let bash interpret/expand this variable without executing it. In other words, I want bash to run eval but not run the evaluated command. Is this possible in bash?

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

How did I manage to pass this into my script without expansion? I passed the argument in surrounding it with double quotes.

我是如何在不扩展的情况下将其传递到我的脚本中的?我用双引号传递了参数。

Try this command to see what I mean:

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

ls -lt "~"

This is exactly the situation I am in. I want the tilde to be expanded. In other words, what should I replace magic with to make these two commands identical:

这正是我所处的情况。我想要扩展波浪号。换句话说,我应该用什么替换 magic 以使这两个命令相同:

ls -lt ~/abc/def/ghi

and

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

Note that ~/abc/def/ghi may or may not exist.

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

采纳答案by wkl

Due to the nature of StackOverflow, I can't just make this answer unaccepted, but in the intervening 5 years since I posted this there have been far better answers than my admittedly rudimentary and pretty bad answer (I was young, don't kill me).

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

The other solutions in this thread are safer and better solutions. Preferably, I'd go with either of these two:

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



Original answer for historic purposes (but please don't use this)

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

If I'm not mistaken, "~"will not be expanded by a bash script in that manner because it is treated as a literal string "~". You can force expansion via evallike this.

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

#!/bin/bash

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

Alternatively, just use ${HOME}if you want the user's home directory.

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

回答by H?kon H?gland

If the variable varis input by the user, evalshould notbe used to expand the tilde using

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

eval var=$var  # Do not use this!

The reason is: the user could by accident (or by purpose) type for example var="$(rm -rf $HOME/)"with possible disastrous consequences.

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

A better (and safer) way is to use Bash parameter expansion:

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

var="${var/#\~/$HOME}"

回答by Charles Duffy

Plagarizing myself from a prior answer, to do this robustly without the security risks associated with eval:

先前的答案中剽窃自己,以稳健地做到这一点,而不会带来与eval以下相关的安全风险:

expandPath() {
  local path
  local -a pathElements resultPathElements
  IFS=':' read -r -a pathElements <<<""
  : "${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%:}"
}

...used as...

...用作...

path=$(expandPath '~/hello')


Alternately, a simpler approach that uses evalcarefully:

或者,一种更简单的方法,eval谨慎使用:

expandPath() {
  case  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' ""
      ;;
  esac
}

回答by eddygeek

A safe way to use eval is "$(printf "~/%q" "$dangerous_path")". Note that is bash specific.

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

#!/bin/bash

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

See this questionfor details

这个问题的详细信息

Also, note that under zsh this would be as as simple as echo ${~dangerous_path}

另外,请注意,在 zsh 下,这就像 echo ${~dangerous_path}

回答by Jay

How about this:

这个怎么样:

path=`realpath ""`

Or:

或者:

path=`readlink -f ""`

回答by Noach Magedman

Expanding (no pun intended) on birryree's and halloleo's answers: The general approach is to use eval, but it comes with some important caveats, namely spaces and output redirection (>) in the variable. The following seems to work for me:

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

mypath=""

if [ -e "`eval echo ${mypath//>}`" ]; then
    echo "FOUND $mypath"
else
    echo "$mypath NOT FOUND"
fi

Try it with each of the following arguments:

尝试使用以下每个参数:

'~'
'~/existing_file'
'~/existing file with spaces'
'~/nonexistant_file'
'~/nonexistant file with spaces'
'~/string containing > redirection'
'~/string containing > redirection > again and >> again'

Explanation

解释

  • The ${mypath//>}strips out >characters which could clobber a file during the eval.
  • The eval echo ...is what does the actual tilde expansion
  • The double-quotes around the -eargument are for support of filenames with spaces.
  • ${mypath//>}剔除>这可能在揍一个文件中的字符eval
  • eval echo ...是什么是实际的波浪线扩展
  • -e参数周围的双引号是为了支持带空格的文件名。

Perhaps there's a more elegant solution, but this is what I was able to come up with.

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

回答by Orwellophile

I believe this is what you're looking for

我相信这就是你要找的

magic() { # returns unexpanded tilde express on invalid user
    local _safe_path; printf -v _safe_path "%q" ""
    eval "ln -sf ${_safe_path#\} /tmp/realpath.$$"
    readlink /tmp/realpath.$$
    rm -f /tmp/realpath.$$
}

Example usage:

用法示例:

$ magic ~nobody/would/look/here
/var/empty/would/look/here

$ magic ~invalid/this/will/not/expand
~invalid/this/will/not/expand

回答by Gino

Here's my solution:

这是我的解决方案:

#!/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 "")

echo "Result = $result"

回答by Paul M

why not delve straight into getting the user's home directory with getent?

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

$ getent passwd mike | cut -d: -f6
/users/mike

回答by mikeserv

Just use evalcorrectly: with validation.

eval正确使用:带验证。

case ${1%%/*} in
([!~]*|""?*[!-+_.[:alnum:]]*|"") ! :;;
(*/*)  set "${1%%/*}" "${1#*/}"       ;;
(*)    set "" 
esac&& eval "printf '%s\n' ${2+/\"$2\"}"