Pour configurer la question, imaginez ce scénario:
mkdir ~/temp
cd ~/
ln -s temp temporary
rm -rf temporary
, rm -f temporary
et rm temporary
chacun enlèveront le lien symbolique mais laisseront le répertoire ~/temp/
.
J'ai un script où le nom du lien symbolique est facilement dérivé, mais le nom du répertoire lié ne l'est pas.
Existe-t-il un moyen de supprimer le répertoire en référençant le lien symbolique, à moins d’analyser le nom du répertoire à partir de ls -od ~/temporary
?
L'utilisation de rm -rf $(readlink temporary)
présente certains défauts: elle échouera si le nom du répertoire contient des espaces. Essayez le:
$ 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/
et est dangereux si les répertoires I
, have
et spaces
existent déjà.
Ajout de citations aide:
$ rm -rf "$(readlink test)"
$ ls -log
lrwxrwxrwx 1 14 Oct 31 19:19 test -> I have spaces/
(montré en lecture, puisque le lien symbolique ne pointe plus vers rien).
Pourtant, il y a toujours un cas où cela échoue. Regardez
$ 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?
Quoi??????
Alors qu'est-ce que je peux faire?
Si vous utilisez bash, une bonne stratégie est la suivante: utilisez cd
avec l'option -P
, pour cd
dans le répertoire après le déréférencement de tous les liens. Regardez la différence:
$ cd test
$ pwd
/home/gniourf/lalala/test
$ cd ..
$ cd -P test
$ pwd
/home/gniourf/lalala/a dir with a trailing newline
$ # Cool!
Tapez help cd
pour plus d’aide sur la fonction intégrée cd
bash. Vous lirez que cd
possède un autre commutateur, à savoir -L
qui est celui utilisé par défaut.
Maintenant quoi?
Eh bien, la stratégie pourrait consister à cd -P
dans le répertoire et à l'utiliser. Le problème est que je ne peux pas supprimer le répertoire dans lequel je me trouve (bon, en fait, je pourrais , voir le bas de cet article). Essayez le:
$ rm -rf .
rm: cannot remove directory: `.'
Et Sudo ne va pas aider beaucoup ici.
Donc, je pourrais cd -P
dans ce répertoire et enregistrer le répertoire de travail dans une variable, puis revenir à l'endroit où j'étais. C'est une belle idée! Mais comment vous "enregistrez le répertoire de travail actuel dans une variable"? comment dites-vous dans quel répertoire vous vous trouvez? Je suis sûr que vous connaissez tous la commande pwd
. (Oh, je l'ai utilisé il y a 2 minutes). Mais si je le fais:
$ saved_dir=$(pwd)
Je vais avoir le même problème à cause de la nouvelle ligne. Heureusement, Bash a la variable PWD
qui contient automatiquement le répertoire de travail (avec la nouvelle ligne de fin, si nécessaire).
Ok, à ce stade, il y a quelque chose que j'aimerais vous dire. Comment connaissez-vous le contenu exact d'une variable? echo
ne suffit pas. Regardez:
$ a="lalala " # with 4 trailing spaces
$ echo "$a"
lalala
$ # :(
Une astuce consiste à utiliser ceci:
$ a="lalala "
$ echo "'$a'"
'lalala '
$ # :)
Cool. Mais parfois, vous ne pourrez pas tout voir. Une bonne stratégie consiste à utiliser la fonction intégrée printf
de bash avec son modificateur %q
. Regardez:
$ a="lalala "
$ printf '%q\n' "$a"
lalala\ \ \ \
$ # :)
Application:
$ 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
Incroyable!
Donc, une stratégie pour supprimer ce répertoire est la suivante:
$ 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?
$ # :)
Terminé!
Maintenant, attendez, c'était quoi ce cd -
? Eh bien, cela signifie simplement que: retourne à l’endroit où j’étais avant cd
ing ici. :)
Vous voulez mettre cela dans une fonction? Allons-y:
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"
}
Terminé!
Remarquez que j'utilise --
juste au cas où le répertoire commence par un trait d'union, afin de ne pas confondre cd
et rm
. Ou,
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"
}
si vous ne voulez pas utiliser cd -
dans la fonction. Mieux encore, bash
possède cette variable OLDPWD
qui contient le répertoire dans lequel vous vous trouviez avant le dernier cd
(cette variable est utilisée pour cd -
). Ensuite, vous pouvez l’utiliser pour éviter les utilisations des variables afin de sauvegarder les emplacements comme dans (mais cela est un peu déroutant):
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"
}
Un effet secondaire de cette méthode est qu’elle changera votre pile de répertoires (donc, après avoir exécuté cette fonction, cd -
ne fonctionnera pas tout à fait comme vous le voudriez).
Bon, il y a encore une chose que j'aimerais vous dire. Il utilisera en fait printf
avec son modificateur %q
, et utilisera également la terrible, la méchante eval
commande. Juste parce que c'est drôle. Cela aura l'avantage de ne pas avoir besoin de cd
retour. Nous appellerons cd
depuis un sous-shell. Oh, tu connais les sous-coques, n'est-ce pas? C'est la petite chose mignonne entourée de parenthèses. Lorsque quelque chose se produit dans un sous-shell, le Shell parent ne le voit pas. Regardez:
$ a=1
$ echo "$a"
1
$ ( a=42 ) # <--- in a subshell!
$ echo "$a"
1
$ # :)
Maintenant cela fonctionne aussi avec cd
:
$ pwd
/home/gniourf/lalala
$ ( cd ~ ; pwd )
/home/gniourf
$ pwd
/home/gniourf/lalala
$ # :)
C'est pourquoi, parfois, dans les scripts, vous verrez des tâches effectuées dans des sous-shell, juste pour ne pas déranger l'état global. Par exemple, lorsque vous vous amusez avec IFS
. C'est généralement considéré comme une bonne pratique.
Revenons à notre sujet: comment allons-nous récupérer la valeur de PWD
dans un sous-shell, puisque je viens de vous montrer que les variables définies dans un sous-shell ne peuvent pas être vues par le shell parent. C'est là que nous utiliserons printf
avec son modificateur %q
:
$ ( cd test; printf '%q' "$PWD" )
$'a dir with a trailing newline\n'
$ # :)
Maintenant, pouvons-nous mettre la sortie standard d'un sous-shell dans une variable? Bien sûr, il suffit de préfixer le sous-shell par un $
comme:
$ labas=$(cd test; printf '%q' "$PWD")
$ echo "$labas"
$'a dir with a trailing newline\n'
$ # :)
( là-bas signifie là-bas .
Mais qu'est-ce qu'on va faire avec cette ficelle ficelle? Facile, nous allons utiliser le terrible, le mal eval
. Vraiment, ne jamais, jamais, jamais utiliser eval
. C'est mauvais. Dieu tue un chaton chaque fois que vous utilisez eval
. Vraiment, crois moi. À moins que vous ne sachiez exactement ce que vous faites, bien sûr. Sauf si vous êtes absolument sûr que vos données ont été désinfectées. (Vous connaissez Bobby Tables?). Mais ici, c'est le cas. Le modificateur de printf
%q
le fait juste pour nous! Ensuite:
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"
}
Regardez, il y a un $labal
non cité et un eval
! Vous devriez vous sentir vraiment bizarre au fond de vous quand vous voyez cela! (c'est mon cas). C'est pourquoi j'ai ajouté le commentaire. Donc, plus tard, je réaliserai que c'est bien.
Il y a une mise en garde, cependant. La partie qui vérifie que nous ne supprimons pas notre répertoire actuel est défectueuse, comme indiqué par:
$ 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! :(
Pour résoudre ce problème, nous devons supprimer le PWD
, l'échapper avec printf '%q'
et le comparer à l'élément déréférencé et échappé que nous souhaitons supprimer. Cela rend le code légèrement plus complexe:
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"
}
( ici signifie ici ).
C’est la fonction que j’aimerais utiliser (jusqu’à ce que j’ai trouvé qu’il y avait encore des défauts ... si vous en trouvez, merci de me le faire savoir dans les commentaires).
J'ai déjà dit qu'une solution intéressante consisterait à cd -P
dans le répertoire que je veux supprimer et à supprimer si, à partir de là, vous utilisez rm -rf .
, mais cela ne fonctionne pas. Eh bien, une méthode vraiment sale serait de rm -rf -- "$PWD"
à la place. C'est horriblement sale mais très amusant:
rm_where_this_points_to_dirty() {
( cd -P -- "$1" && rm -rfv -- "$PWD" )
}
Terminé!
Utilisez-le à vos risques et périls !!!
À votre santé!
Dans ce message, vous pourriez avoir appris l'un des éléments suivants:
cd -P
v.s. cd -L
.$(...)
.PWD
et OLDPWD
.$'...'
pour avoir des effets sympas.printf
et son modificateur %q
.--
.eval
. Jamais.Vous pouvez utiliser la commande suivante:
rm -rf $(ls -l ~/temporary | awk '{print $NF}')
Si vous ne voulez pas analyser ls
, vous pouvez utiliser:
rm -rf $(file ~/temporary | awk '{print $NF}' | tr -d "\`\',")
ou simple:
rm -rf $(readlink ~/temporary)
Pour prendre soin des espaces tant dans le nom du répertoire que dans le nom du lien, vous pouvez modifier la dernière commande comme suit
rm -rf "$(readlink ~/"temporary with spaces")"