为了提出这个问题,想象一下这个场景:
mkdir ~/temp
cd ~/
ln -s temp temporary
rm -rf temporary
、、rm -f temporary
和rm temporary
each 将删除符号链接但保留目录~/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/
如果目录I
、have
和spaces
已经存在,则很危险。
添加引号有助于:
$ 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
选项-P
,cd
在所有链接取消引用后进入目录。观察差异:
$ 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 来防止目录以连字符开头,以免混淆cd
和rm
。或者,
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 中取回PWD
while 的值,因为我刚刚向您展示了子 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 -P
与cd -L
- 使用时,结尾的换行符可能会造成灾难性的后果命令替换, IE,
$(...)
。 - 变量
PWD
和OLDPWD
。 - 使用
$'...'
会产生一些很酷的效果。 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")"