Comment nettoyer ou échapper les chemins absolus renvoyés par realpath ou readlink?

9

realpathet readlinkretourner des chemins absolus:

+akiva@X230:~$ realpath ZannaIsAwesome
/home/akiva/ZannaIsAwesome

Un chemin comme celui-là est facile à gérer. Cependant, quelque chose comme ça va avoir des problèmes:

entrez la description de l'image ici

Par exemple:

entrez la description de l'image ici

Un nom comme celui-ci doit donc être purifié afin de pouvoir le nourrir avec d'autres commandes. Un cas d'utilisation pourrait ressembler à ceci:

+a@X230:~/\e[92mM@r|< $hu+'|'|_e|\|\|0rth [`-_-"]$ bacon=$(realpath pullingATerdon)
+a@X230:~$ vim $bacon 

Inutile de dire que vim $baconcela ne fonctionnera pas comme prévu.

Que puis-je faire pour assainir ce chemin absolu afin qu'il fonctionne avec d'autres commandes?

Akiva
la source
3
Citez toujours vos variables:vim "$bacon"
Steeldriver
@steeldriver Pourquoi cela?
Akiva
3
Parce que si vous ne le faites pas, vous vous retrouvez dans des situations comme celle-ci :)
terdon

Réponses:

12

Comment faire cela correctement

Tout d'abord, citez toujours vos variables . Ce que vous essayez de faire fonctionne bien si vous le citez correctement:

$ pwd
/home/terdon/foo/\e[92mM@r|< +'|'|_e|\|\|0rth [`-_-"]
$ ls
pullingATerdon

J'ai conservé le nom de fichier étrange que vous avez choisi ( bien que je ne sache pas pourquoi vous l'avez choisi ) par souci de cohérence.

Maintenant, attribuons le chemin de pullingATerdonà une variable, puis essayons d'ouvrir le fichier:

$ bacon="$(realpath pullingATerdon)"
$ echo "$bacon"
/home/terdon/foo/\e[92mM@r|< +'|'|_e|\|\|0rth [`-_-"]/pullingATerdon
$ ls $bacon
ls: cannot access '+'\''|'\''|_e|\|\|0rth': No such file or directory
ls: cannot access '[`-_-"]/pullingATerdon': No such file or directory
'/home/terdon/foo/\e[92mM@r|<':

Cela échoue, comme prévu. Mais, si nous le citons maintenant correctement:

$ ls -l "$bacon"
-rw-r--r-- 1 terdon terdon 0 Mar 14 23:15 '/home/terdon/foo/\e[92mM@r|< +'\''|'\''|_e|\|\|0rth [`-_-"]/pullingATerdon'

Cela fonctionne comme prévu. Et oui, vous pouvez également ouvrir le chemin dans un (bon) éditeur: emacs "$bacon"cela fonctionnera très bien. OK, et vimainsi de suite. Votre choix d'éditeur, bien que malheureux, n'est pas pertinent.


Pourquoi le vôtre a échoué

Un moyen rapide de retracer ce qui s'est réellement passé dans votre cas est d'utiliser set -x(désactivez-le à nouveau avec set +x), ce qui oblige le shell à imprimer chaque commande qu'il exécutera avant de l'exécuter. activez les messages de débogage du shell avec set -x:

$ set -x
$ /bin/ls $bacon 
+ ls '/home/terdon/foo/\e[92mM@r|<' '+'\''|'\''|_e|\|\|0rth' '[`-_-"]/pullingATerdon'
ls: cannot access '+'\''|'\''|_e|\|\|0rth': No such file or directory
ls: cannot access '[`-_-"]/pullingATerdon': No such file or directory
'/home/terdon/foo/\e[92mM@r|<':

Cela nous montre que lsa été exécuté avec trois arguments distincts: '/home/terdon/foo/\e[92mM@r|<', '+'\''|'\''|_e|\|\|0rth'et '[`-_-"]/pullingATerdon'. Cela se produit car le shell effectue le fractionnement de mots et l'expansion globale sur les chaînes non entre guillemets. Dans ce cas, le problème est la division des mots, car le shell a vu les espaces dans le chemin d'accès et a lu chaque chaîne séparée par des espaces comme un argument distinct.

L' mkdirexemple est légèrement différent mais c'est parce que vous nous montrez le message d'erreur de la deuxième invocation de la commande. Je suppose que vous l'avez essayé une fois, puis l'avez exécuté une deuxième fois pour obtenir le résultat de votre question. La première fois que vous l'avez exécuté, cela aurait ressemblé à ceci:

$ mkdir $(realpath pullingATerdon)
++ realpath pullingATerdon
+ mkdir '/home/terdon/foo/\e[92mM@r|<' '+'\''|'\''|_e|\|\|0rth' '[`-_-"]/pullingATerdon'
mkdir: cannot create directory ‘[`-_-"]/pullingATerdon’: No such file or directory

Encore une fois, cela tentera de créer trois répertoires, pas un, en raison de la division des mots. Tout d'abord, il a créé (avec succès) le répertoire /home/terdon/foo/\e[92mM@r|<:

$ ls -l /home/terdon/foo/
total 8
drwxr-xr-x 2 terdon terdon 4096 Mar 15 00:20 '\e[92mM@r|<'
drwxr-xr-x 3 terdon terdon 4096 Mar 15 00:20 '\e[92mM@r|< +'\''|'\''|_e|\|\|0rth [`-_-"]'

Il a ensuite, également avec succès, créé un répertoire appelé +'|'|_e|\|\|0rthdans votre répertoire actuel:

$ ls -l
total 4
drwxr-xr-x 2 terdon terdon 4096 Mar 15 00:37 '+'\''|'\''|_e|\|\|0rth'
-rw-r--r-- 1 terdon terdon    0 Mar 15 00:36  pullingATerdon

Et puis, il a tenté de créer le répertoire [`-_-"]/pullingATerdon. Cela a échoué car mkdir, par défaut, ne crée pas de sous-répertoires (il peut, si vous l'exécutez avec -p):

$ mkdir baz/bar
mkdir: cannot create directory baz/bar’: No such file or directory

Étant donné que votre chaîne sans guillemets contenait un /, mkdirconsidéré comme un chemin d'accès à deux répertoires, a essayé de trouver le premier et a échoué.

C'est pourquoi cela a échoué, mais ce qui s'est passé est plus compliqué. La chaîne que vous avez utilisé est en fait un glob shell, en particulier une gamme de glob , qui correspond à tous les fichiers dans le répertoire courant dont le nom est l' un des 5 personnages `, -, _ou ". Puisque vous n'avez pas de tels fichiers dans votre répertoire actuel, le glob ne correspond à rien et, comme c'est le comportement par défaut dans bash, se retourne:

$ echo "[\`-_-\"]/pullingATerdon"  ## some escaping is needed here
+ echo '[`-_-"]/pullingATerdon'    ## but it echoes the right thing
[`-_-"]/pullingATerdon             ## and matches nothing, so returns itself.

Pour clarifier, voici ce qui se passe si vous donnez un glob qui correspond à quelque chose:

$ echo [p]*   ## any filename starting with a p
pullingATerdon
$ echo "[p]*" ## the string "[p]*"
[p]*

Le non cité [p*]est étendu à la liste des noms de fichiers correspondants (un seul, dans ce cas) et c'est ce qui est transmis à echo. Encore une autre raison pour laquelle vous devriez citer toutes ces choses.

Enfin, l'erreur réelle que vous affichez est la deuxième fois que vous avez exécuté la commande et elle échoue à la première étape, lors de la tentative de création /home/terdon/foo/\e[92mM@r|<, car l'invocation précédente avait déjà créé ce répertoire.


Plus généralement, chaque fois que vous travaillez avec des noms de fichiers arbitraires, utilisez toujours des globes shell. Des choses comme ça:

for file in *; do command "$file"; done

Cela fonctionnera pour n'importe quel nom de fichier. Peu importe ce qu'il contient. Dans notre exemple ci-dessus, vous auriez pu faire:

emacs /home/terdon/*92mM*/pullingATerdon

Tout glob qui identifie le fichier cible de manière unique fera l'affaire. De cette façon, vous n'avez pas à vous soucier des caractères spéciaux et vous pouvez simplement laisser le shell les gérer.


Quelques références utiles:

  1. Comment puis-je trouver et gérer en toute sécurité les noms de fichiers contenant des sauts de ligne, des espaces ou les deux? : Une des FAQ sur l'excellent wiki de Gray Cat.

  2. Conséquences pour la sécurité d'oublier de citer une variable dans des shells bash / POSIX : le même post auquel j'ai fait référence au début de cette réponse. Une excellente et très détaillée explication de tout ce qui pourrait mal tourner si vous ne citez pas correctement vos variables shell.

  3. Pourquoi mon script shell s'étouffe-t-il sur les espaces ou autres caractères spéciaux? : tout ce que vous avez toujours voulu savoir sur la gestion des noms de fichiers arbitraires dans le shell.

  4. Quand faut-il citer deux fois? : En savoir plus sur les citations et les variables et, en particulier, sur les quelques cas où vous n'avez pas besoin de les citer

terdon
la source