通过引用符号链接删除目录

Ada*_*dam 3 command-line scripts rm symbolic-link

要设置问题,请想象以下场景:

mkdir ~/temp
cd ~/
ln -s temp temporary
Run Code Online (Sandbox Code Playgroud)

rm -rf temporary, rm -f temporary, 和rm temporaryeach 将删除符号链接但离开目录~/temp/

我有一个脚本,其中符号链接的名称很容易派生,但链接目录的名称不是。

有没有办法通过引用符号链接来删除目录,而不是从中解析目录的名称ls -od ~/temporary

Rad*_*anu 5

您可以使用以下命令:

rm -rf $(ls -l ~/temporary | awk '{print $NF}')
Run Code Online (Sandbox Code Playgroud)

如果你不想解析ls,那么你可以使用:

rm -rf $(file ~/temporary | awk '{print $NF}' | tr -d "\`\',")
Run Code Online (Sandbox Code Playgroud)

或简单:

rm -rf $(readlink ~/temporary)
Run Code Online (Sandbox Code Playgroud)

为了兼顾目录名和链接名中的空格,可以修改最后一条命令如下

rm -rf "$(readlink ~/"temporary with spaces")"
Run Code Online (Sandbox Code Playgroud)


gni*_*urf 5

使用rm -rf $(readlink temporary)有一些缺陷:如果目录名有空格会失败。尝试一下:

$ mkdir 'I have spaces'
$ ln -s 'I have spaces' test
$ ls -log
drwxr-xr-x 2 4096 Oct 31 19:19 I have spaces
lrwxrwxrwx 1   14 Oct 31 19:19 test -> I have spaces/
$ rm -rf $(readlink test)
$ ls -log
drwxr-xr-x 2 4096 Oct 31 19:19 I have spaces
lrwxrwxrwx 1   14 Oct 31 19:19 test -> I have spaces/
Run Code Online (Sandbox Code Playgroud)

如果目录I,havespaces已经存在,则是危险的。

添加引号有助于:

$ rm -rf "$(readlink test)"
$ ls -log
lrwxrwxrwx 1 14 Oct 31 19:19 test -> I have spaces/
Run Code Online (Sandbox Code Playgroud)

(显示在 read 中,因为符号链接现在不指向任何内容)。

然而,仍然有失败的情况。看

$ mkdir $'a dir with a trailing newline\n'
$ ln -s $'a dir with a trailing newline\n' test
$ ls -log
drwxr-xr-x  2 4096 Oct 31 19:30 a dir with a trailing newline?
lrwxrwxrwx  1   31 Oct 31 19:30 test -> a dir with a trailing new line?
$ rm -rf "$(readlink test)"
$ ls -log
drwxr-xr-x  2 4096 Oct 31 19:30 a dir with a trailing newline?
lrwxrwxrwx  1   31 Oct 31 19:30 test -> a dir with a trailing new line?
Run Code Online (Sandbox Code Playgroud)

什么??????

那么,我能做什么?

如果你正在使用bash,一个好的策略如下:使用cd-P选项,到cd到目录中的所有链接已经被提领后。看区别:

$ cd test
$ pwd
/home/gniourf/lalala/test
$ cd ..
$ cd -P test
$ pwd
/home/gniourf/lalala/a dir with a trailing newline

$ # Cool!
Run Code Online (Sandbox Code Playgroud)

键入help cd有关更多帮助cdbash的内建命令。你会读到它cd有另一个开关,即-L默认使用的开关。

怎么办?

好吧,策略可能是cd -P进入目录,然后使用它。问题是我无法删除我所在的目录(好吧,实际上我可以,请参阅这篇文章的底部)。尝试一下:

$ rm -rf .
rm: cannot remove directory: `.'
Run Code Online (Sandbox Code Playgroud)

而 sudo 在这里不会有太大帮助。

所以我可以cd -P在那个目录中,并将工作目录保存在一个变量中,然后回到我所在的位置。这是个好主意!但是你们如何“将当前的工作目录保存在一个变量中”?你怎么知道你在哪个目录?相信大家都知道这个pwd命令。(哦,我实际上在 2 分钟前使用过它)。但如果我这样做:

$ saved_dir=$(pwd)
Run Code Online (Sandbox Code Playgroud)

由于尾随换行符,我会遇到同样的麻烦。幸运的是,Bash 具有PWD自动包含工作目录的变量(如有必要,带有尾随换行符)。

好的,在这一点上,我想告诉你一些事情。当然,您如何知道变量的确切内容?echo是不足够的。看:

$ a="lalala    " # with 4 trailing spaces
$ echo "$a"
lalala
$ # :(
Run Code Online (Sandbox Code Playgroud)

一个技巧是使用这个:

$ a="lalala    "
$ echo "'$a'"
'lalala    '
$ # :)
Run Code Online (Sandbox Code Playgroud)

凉爽的。但有时您将无法看到所有内容。一个好的策略是将 bash 的内置函数printf与其%q修饰符一起使用。看:

$ a="lalala    "
$ printf '%q\n' "$a"
lalala\ \ \ \ 
$ # :)
Run Code Online (Sandbox Code Playgroud)

应用:

$ cd -P test
$ pwd
a dir with a trailing newline

$ printf '%q\n' "$PWD"
$'a dir with a trailing newline\n'
$ # :)
$ cd .. # let's go back where we were
Run Code Online (Sandbox Code Playgroud)

惊人的!

因此,删除此目录的策略如下:

$ cd -P test
$ saved_dir=$PWD
$ cd -
/home/gniourf/lalala
$ rm -rf "$PWD"
$ ls -log
lrwxrwxrwx 1   31 Oct 31 19:30 test -> a dir with a trailing new line?
$ # :)
Run Code Online (Sandbox Code Playgroud)

完毕!

现在,等等,那是cd -什么?嗯,这只是意味着:回到我在cd这里之前的地方。 :)

你想把它放在一个函数中吗?我们走吧:

rm_where_this_points_to() {
    cd -P -- "$1" || { echo >&2 "Sorry, I couldn't go where \`$1' points to."; return 1; }
    local saved_dir=$PWD
    cd -
    if [[ $saved_dir = $PWD ]]; then
        echo &>2 "Oh dear, \`$1' points here exactly."
        return 1
    fi
    rm -rfv -- "$saved_dir"
}
Run Code Online (Sandbox Code Playgroud)

完毕!

注意我是如何使用的--,以防万一 dir 以连字符开头,以免混淆cdrm。或者,

rm_where_this_points_to() {
    local here=$PWD
    cd -P -- "$1" || { echo >&2 "Sorry, I couldn't go where \`$1' points to."; return 1; }
    local saved_dir=$PWD
    cd -- "$here"
    if [[ "$saved_dir" = "$PWD" ]]; then
        echo >&2 "Oh dear, \`$1' points here exactly."
        return 1
    fi
    rm -rfv -- "$saved_dir"
}
Run Code Online (Sandbox Code Playgroud)

如果你不想cd -在函数内部使用。更好的是,bash这个变量OLDPWD包含你在最后一个之前所在的目录cd(这个变量用于cd -)。然后,您可以使用它来避免使用变量来保存位置(但这有点令人困惑):

rm_where_this_points_to() {
    cd -P -- "$1" || { echo >&2 "Sorry, I couldn't go where \`$1' points to."; return 1; }
    cd -- "$OLDPWD"
    # At this point, OLDPWD is the directory to remove, not the one we're in!
    if [[ "$OLDPWD" = "$PWD" ]]; then
        echo >&2 "Oh dear, \`$1' points to here exactly."
        return 1
    fi
    rm -rfv -- "$OLDPWD"
}
Run Code Online (Sandbox Code Playgroud)

此方法的一个副作用是它会更改您的目录堆栈(因此在执行此功能后,cd -将无法如您所愿)。


好吧,还有一件事我想告诉你。它实际上会printf与它的%q修饰符一起使用,也会使用可怕的、邪恶的eval命令。只是因为很好笑。这将具有不需要cd后退的优点。我们将从cd子shell调用。哦,你知道子壳不是吗?它是用括号括起来的可爱小东西。当子 shell 中发生某些事情时,父 shell 看不到它。看:

$ a=1
$ echo "$a"
1
$ ( a=42 ) # <--- in a subshell!
$ echo "$a"
1
$ # :)
Run Code Online (Sandbox Code Playgroud)

现在这也适用于cd

$ pwd
/home/gniourf/lalala
$ ( cd ~ ; pwd )
/home/gniourf
$ pwd
/home/gniourf/lalala
$ # :)
Run Code Online (Sandbox Code Playgroud)

这就是为什么有时,在脚本中,您会看到在子 shell 中完成的事情,以免弄乱全局状态。例如,当与IFS. 这通常被认为是一种很好的做法。

回到我们的事情:我们将如何PWD在子 shell 中取回while的值,因为我刚刚向您展示了在子 shell 中设置的变量不能被父 shell 看到。这就是我们将使用printf它的%q修饰符的地方:

$ ( cd test; printf '%q' "$PWD" )
$'a dir with a trailing newline\n'
$ # :)
Run Code Online (Sandbox Code Playgroud)

现在,我们可以将子shell的标准输出放在一个变量中吗?当然,只需在子 shell 前面加上一个$as:

$ labas=$(cd test; printf '%q' "$PWD")
$ echo "$labas"
$'a dir with a trailing newline\n'
$ # :)
Run Code Online (Sandbox Code Playgroud)

là-bas在法语中的意思是那边)。

但是我们要怎么处理这个愚蠢的字符串呢?很简单,我们将使用可怕的、邪恶的eval。真的,永远不要,永远,永远不要使用eval. 这不好。每次使用时上帝都会杀死一只小猫eval。真的,相信我。当然,除非你完全知道自己在做什么。除非您绝对确定您的数据已被清理。(你知道鲍比桌吗?)。但在这里,情况就是这样。printf的修饰符%q只是为我们做这件事!然后:

rm_where_this_points_to() {
    local labas=$( cd -P -- "$1" && printf '%q' "$PWD" )
    [[ -z "$labas" ]] && { echo >&2 "Couldn't go where \`$1' points to."; return 1; }
    if [[ "$labas" = "$(printf '%q' "$PWD")" ]]; then
        echo >&2 "Oh dear, \`$1' points here exactly."
        return 1
    fi
    # eval and unquoted $labas, I know what I'm doing:
    # $labas has been sanitized and comes directly from `printf '%q'`
    eval "rm -rfv -- $labas"
}
Run Code Online (Sandbox Code Playgroud)

看,有一个未引用的$labal和一个eval!当你看到这个时,你应该在内心深处感到非常奇怪!(这就是我的情况)。这就是我添加评论的原因。所以以后我会意识到这很好。


不过,有一个警告。检查我们没有删除当前目录的部分存在缺陷,如下所示:

$ ln -s . wtf
$ rm_where_this_points_to wtf
Oh dear, `wtf' points here exactly.
$ # Good :) but look:
$ cd wtf
$ rm_where_this_points_to wtf
$ # Oh, it did it! :(
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,我们需要取消引用PWD,转义它,printf '%q'并将它与我们想要删除的取消引用和转义的东西进行比较。它只会使代码稍微复杂一些:

rm_where_this_points_to() {
    local labas=$( cd -P -- "$1" && printf '%q' "$PWD" )
    local ici=$( cd -P . && printf '%q' "$PWD" )
    [[ -z "$labas" ]] && { echo >&2 "Couldn't go where \`$1' points to."; return 1; }
    [[ -z "$ici" ]] && { echo >&2 "Something really weird happened: I can't dereference the current directory."; return 1; }
    if [[ "$labas" = "$ici" ]]; then
        echo >&2 "Oh dear, \`$1' points here exactly."
        return 1
    fi
    # eval and unquoted $labas, I know what I'm doing:
    # $labas has been sanitized and comes directly from `printf '%q'`
    eval "rm -rfv -- $labas"
}
Run Code Online (Sandbox Code Playgroud)

ICI意味着这里法文)。

这是我将使用的功能(直到我发现它仍然有一些缺陷......如果你发现任何缺陷,请在评论中告诉我)。


我之前说过一个很酷的解决方案是到cd -P我要删除的目录,然后使用 删除 if rm -rf .,但这不起作用。好吧,一个非常肮脏的方法是rm -rf -- "$PWD"改为。这太脏了,但很有趣:

rm_where_this_points_to_dirty() {
    ( cd -P -- "$1" && rm -rfv -- "$PWD" )
}
Run Code Online (Sandbox Code Playgroud)

完毕!

使用风险自负!!!

干杯!


在这篇文章中,您可能已经了解了以下内容之一:

  • 使用更多的报价!
  • 使用更多的报价!
  • cd -Pcd -L.
  • 使用命令替换时尾随换行符可能是灾难性的,即$(...).
  • 变量PWDOLDPWD.
  • 要使用$'...'有一些很酷的效果。
  • printf及其%q修饰符。
  • 使用--.
  • 子壳。
  • 那边这边用法语怎么说。
  • 从不使用eval. 绝不。