web-dev-qa-db-fra.com

Comment puis-je nettoyer ou échapper aux chemins absolus renvoyés par realpath ou readlink?

realpath et readlink renvoient des chemins absolus:

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

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

enter image description here

Par exemple:

enter image description here

Ainsi, un nom comme celui-ci doit être nettoyé pour pouvoir le transmettre à d'autres commandes. Une 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 $bacon ne fonctionnera pas comme prévu.

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

8
Akiva

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 gardé 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 assignons le chemin de pullingATerdon à une variable et 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 éditeur (approprié): emacs "$bacon" fonctionnera parfaitement. OK, ainsi vim et toute autre chose. Votre choix d'éditeur, bien que malheureux, n'est pas pertinent.


Pourquoi le tien a-t-il échoué?

Un moyen rapide de retracer ce qui s'est réellement passé dans votre cas consiste à utiliser set -x (le désactiver à nouveau avec set +x), ce qui oblige le shell à imprimer chaque commande à exécuter avant de l'exécuter. activer 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 ls a été exécuté avec trois arguments distincts: '/home/terdon/foo/\e[92mM@r|<', '+'\''|'\''|_e|\|\|0rth' et '[`-_-"]/pullingATerdon'. Cela est dû au fait que le shell effectue fractionnement de mots et expansion globale sur des chaînes non entre guillemets. Dans ce cas, le problème est le fractionnement de Word, car le shell a vu les espaces dans le chemin et a lu chaque chaîne séparée par des espaces en tant qu'argument séparé.

L'exemple mkdir est légèrement différent, mais c'est parce que vous nous affichez le message d'erreur de l'invocation de la commande second. Je suppose que vous avez essayé une fois, puis l'exécutez une seconde fois pour obtenir le résultat de votre question. La première fois que vous l'avez lancée, 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 va essayer de créer trois répertoires, pas un, à cause du fractionnement de Word. 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 créé avec succès un répertoire appelé +'|'|_e|\|\|0rth dans 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 /, mkdir considérait qu'un chemin de deux répertoires, essayait de trouver le premier et échouait.

C'est pourquoi cela a échoué, mais ce qui s'est passé est plus compliqué. La chaîne que vous avez utilisée est en fait un glob Shell, en particulier plage de glob , qui correspond à tous les fichiers du répertoire en cours dont le nom correspond à l'un des 5 caractères `, -, _ ou ". Comme vous n'avez pas de tels fichiers dans votre répertoire actuel, le glob ne correspond à rien et, comme le comportement par défaut dans bash, se retourne tout seul:

$ 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 [p*] non entre guillemets est étendu à la liste des noms de fichiers correspondants (un seul dans ce cas) et c’est ce qui est passé à echo. Encore une autre raison pour laquelle vous devriez citer toutes les choses.

Enfin, l'erreur réelle que vous indiquez est celle qui se produit à partir de la deuxième exécution de la commande et échoue lors de la première étape lors de la tentative de création de /home/terdon/foo/\e[92mM@r|<, car l'appel précédent 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 globs de 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 arrive à contenir. 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 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 nouvelles lignes, des espaces ou les deux? : Une des FAQ sur l'excellent Wiki de Grey Cat.

  2. Répercussions sur la sécurité de l’oubli de citer une variable dans les shells POSH/bash : le même message que j’ai référencé au début de cette réponse. Une excellente explication très détaillée de tout ce qui pourrait mal se passer si vous ne citez pas correctement vos variables Shell.

  3. Pourquoi mon script shell s'étouffe-t-il avec des espaces ou d'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 faire des doubles guillemets? : En savoir plus sur les guillemets et les variables et, en particulier, sur les rares cas où vous n'avez pas besoin de les citer

11
terdon