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

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

为了提出这个问题,想象一下这个场景:

mkdir ~/temp
cd ~/
ln -s temp temporary

rm -rf temporary、、rm -f temporaryrm temporaryeach 将删除符号链接但保留目录~/temp/

我有一个脚本,其中符号链接的名称很容易得出,但链接目录的名称却不能。

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

答案1

使用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/

如果目录Ihavespaces已经存在,则很危险。

添加引号有助于:

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

(显示为已读,因为符号链接现在指向零)。

然而,还是有失败的情况。看看

$ 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?

什么??????

那么,我该怎么办?

如果您使用 bash,则一个好的策略是:使用cd选项-Pcd在所有链接取消引用后进入目录。观察差异:

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

$ # Cool!

输入help cd以获取有关 bash 内置命令的更多帮助cd。您将看到cd有另一个开关,即-L默认使用的开关。

怎么办?

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

$ rm -rf .
rm: cannot remove directory: `.'

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

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

$ saved_dir=$(pwd)

我会因为尾随换行符而遇到同样的麻烦。幸运的是,Bash 有一个变量PWD,可以自动包含工作目录(如果需要,带有尾随换行符)。

好的,现在我想告诉你一件事。你如何确定知道变量的确切内容?echo这还不够。看:

$ a="lalala    " # with 4 trailing spaces
$ echo "$a"
lalala
$ # :(

一个技巧是使用这个:

$ a="lalala    "
$ echo "'$a'"
'lalala    '
$ # :)

很酷。但有时你无法看到所有内容。一个好的策略是将 bash 的内置命令printf与其%q修饰符一起使用。看:

$ a="lalala    "
$ printf '%q\n' "$a"
lalala\ \ \ \ 
$ # :)

应用:

$ 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

惊人的!

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

$ 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?
$ # :)

完毕!

现在,等一下,那是什么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"
}

完毕!

请注意,我使用--just 来防止目录以连字符开头,以免混淆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"
}

如果您不想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"
}

此方法的一个副作用是它将改变您的目录堆栈(因此执行此功能后,cd -将不会像您希望的那样工作)。


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

$ a=1
$ echo "$a"
1
$ ( a=42 ) # <--- in a subshell!
$ echo "$a"
1
$ # :)

现在这也适用于cd

$ pwd
/home/gniourf/lalala
$ ( cd ~ ; pwd )
/home/gniourf
$ pwd
/home/gniourf/lalala
$ # :)

这就是为什么有时在脚本中,你会看到在子 shell 中完成一些操作,只是为了不干扰全局状态。例如,当干扰时IFS。这通常被认为是一种良好做法。

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

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

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

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

拉巴斯方法在那边法语)。

但是我们要怎么处理这个愚蠢的字符串呢?简单,我们将使用可怕的,邪恶的eval。真的,永远不要使用eval。这很糟糕。每次你使用 ,上帝都会杀死一只小猫eval。真的,相信我。当然,除非你确切地知道你在做什么。除非你绝对确定你的数据已经被净化了。(你知道Bobby Tables吗?)。但在这里,情况就是这样。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"
}

看,有一个不带引号的$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! :(

为了解决这个问题,我们需要取消引用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"
}

伊西方法这里法语)。

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


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

rm_where_this_points_to_dirty() {
    ( cd -P -- "$1" && rm -rfv -- "$PWD" )
}

完毕!

使用风险请自负!!!

干杯!


在这篇文章中你可能已经学会了下列其中一项:

  • 使用更多引号!
  • 使用更多引号!
  • cd -Pcd -L
  • 使用时,结尾的换行符可能会造成灾难性的后果命令替换, IE,$(...)
  • 变量PWDOLDPWD
  • 使用$'...'会产生一些很酷的效果。
  • printf及其%q修饰语。
  • 使用--
  • 子壳层。
  • 怎么说在那边这里用法语。
  • 永远不要使用eval。永远不要。

答案2

您可以使用以下命令:

rm -rf $(ls -l ~/temporary | awk '{print $NF}')

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

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

或简单:

rm -rf $(readlink ~/temporary)

为了处理目录名称和链接名称中的空格,您可以修改最后一个命令,如下所示

rm -rf "$(readlink ~/"temporary with spaces")"

相关内容