切换目录到以波浪号表示的文件路径中指定的位置

4

我有一个文件,例如:~/cwd。这个文件的内容是一行:

~/tmp

我想要将当前目录切换到(~/tmp)目录。我尝试使用以下命令:

> cd `cat ~/cwd`

并得到了:
-bash: cd: ~/tmp: No such file or directory

为什么相对路径失败了?当 ~/cwd 的内容是绝对路径时 - 它可以工作。

2
~/cwd仍然是一个绝对路径;~只是当前用户主目录的绝对路径的简写。它适用于交互式使用,而不适用于脚本编写。如果您对文件有控制权(如果您甚至考虑使用eval,那么您应该有控制权),那么您应该完整地写出目录名,这样cd $(< ~/cwd)就可以工作了。 - chepner
4个回答

5

这不是相对路径的问题——这是因为shell评估模型在参数扩展之前进行了波浪线扩展。使用eval跳回到评估过程的开头会引入安全漏洞,因此如果确实需要支持此功能(我强烈反对这样做),则一个安全的实现(针对具有getent命令的平台)应如下所示:

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%:}"
}

...安全地从文件中读取路径,可以使用以下方法:

printf '%s\n' "$(expandPath "$(<file)")"

或者,一种更简单的方法是谨慎地使用 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
}

username不能与变量一起使用: `path="root"; printf '%s\n' "$(expandPath "$path")"返回~root而不是/root。但如果我这样做printf '%s\n' "$(expandPath ~root)"` 就能正常工作。 - Jahid
我使用set -x开始跟踪,并注意到未使用的变量 assetsPath :D... 感谢您的快速响应... - Jahid
考虑到 eval 的安全隐患,这应该是被接受的答案。 - codeforester

3

试试这个:

eval cd `cat ~/cwd`

"

‘~’需要由Shell扩展。eval使命令通过shell的命令处理运行,其中包括‘~’的扩展。

"

谢谢,它有效!但是为什么 'cd cat ~/cwd' 不行呢?cat ~/cwd 给出了一个字符串,'cat' 应该能够正确解释它。 - egor7
1
请记住,''由shell解释,而不是cat。因此,"/cwd"首先由shell扩展为路径,然后传递给cat。cat的输出也包含'',这也需要被shell扩展。使用eval会导致shell通过其命令处理运行它,从而扩展''。 - Ziffusion
3
它能够工作,但你最好相信这个文件不会有恶意。如果它包含 $(rm -rf /)/cwd,那么情况就非常糟糕了。 - Charles Duffy

2

使用 eval

eval cd $(cat file)

否则,Shell 将无法解释 ~ 的含义。

好观点,@CharlesDuffy。我们能做什么?comm=$(cat file); [[ ! "$comm" == *rm* ]] && eval cd "$comm" - fedorqui
1
我倾向于手动处理前导~的情况并且放弃使用eval。 - Charles Duffy
我已经添加了一个独立的答案来实现它。 - Charles Duffy

1
不需要使用cat
eval cd "$(<~/cwd)"

2
+1 不使用 cat,-1 如果没有关于信任的警告建议使用 eval。 - Charles Duffy
@CharlesDuffy 大家都这样做了,而且这只是一个微不足道的问题。此外,得出结论认为某人可能没有足够聪明意识到在简单测试文件上使用 eval 的可能后果,只能是别人的担心或看法。我的回答只是对他们答案的更新。 - konsolebox
1
如果其他人都跳桥,你会跟着跳吗?但说真的,这个问题之所以出现是因为人们不理解Bash的评估模型(不同扩展规则之间的评估顺序)。如果有人不明白这一点,那么假设他们不了解跳回到评估链的开头所带来的安全隐患是合适的。 - Charles Duffy
@CharlesDuffy 对于那个显而易见的引用,我确实意识到提及eval的细节的必要性,但我选择不这样做,因为我认为对于一个简单的问题来说并不足够必要。再次考虑在这种话题中的安全性只是一种观点。希望你不会因此得出我在这些问题上不谨慎的结论。我在SO上回答了许多使用eval的脚本,请尝试看看是否能找到其中的缺陷。当然,除了这个问题。 - konsolebox
1
我对你的要求比大多数人都更高,因为你展示了比平均水平更高的技能。把它当作一种赞美吧。 :) - Charles Duffy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接