Donc, faire quelque chose comme ça dans bash
et la plupart des autres shells ne fonctionneront pas pour créer plusieurs sous-répertoires ou fichiers dans des sous-répertoires…
mkdir */test
touch */hello.txt
Il existe bien sûr de nombreuses façons de le faire, ma préférée est d'utiliser find
lorsque cela est possible plutôt que d'utiliser un for
boucle, pour la lisibilité principalement.
Mais ma question est la suivante :pourquoi ce qui précède ne fonctionne-t-il pas ?
D'après ce que je comprends, c'est parce que le fichier/chemin de destination complet n'existe pas, mais c'est sûrement une bonne chose si j'essaie de mkdir
ou touch
. Je suis toujours passé à autre chose et je ne l'ai jamais vraiment remis en question.
Mais est-ce que quelqu'un a une explication décente pour cela qui m'aidera à comprendre une fois pour toutes ?
Réponse acceptée :
Le point qui vous manque peut-être - avec lequel beaucoup de gens ont des problèmes,
surtout s'ils ont de l'expérience avec d'autres systèmes d'exploitation avant de venir à *nix
- est que, dans de nombreux autres systèmes d'exploitation, les caractères génériques sur le ligne de commande sont normalement passés à la commande traiter comme bon lui semble. Par exemple, dans l'invite de commande Windows,
rename *.jpeg *.jpg
Alors que, dans *nix, afin de simplifier le travail
du ou des programmeurs des commandes individuelles (par exemple, mv
), les caractères génériques
(lorsqu'ils ne sont ni entre guillemets ni échappés) sont gérés par le shell,
d'une manière indépendante de l'interface et de la fonctionnalité
de la commande dont les caractères génériques sont les arguments.
La plupart des programmes qui prennent des noms de fichiers comme arguments de ligne de commande s'attendent à ce que ces fichiers existent
(oui, mkdir
et touch
sont des exceptions à cette règle,
tout comme mkfifo
, mknod
, et, dans une certaine mesure, cp
, ln
, mv
, et rename
;
et il y en a probablement d'autres),
il n'est donc pas vraiment logique que le shell étende les caractères génériques
aux noms de fichiers qui n'existent pas.
Et pour le shell (et, par là, je veux dire tous shell –
Bourne, bash, csh, fish, ksh, zsh, etc…) gérer les exceptions différemment
serait probablement trop compliqué.
Cela dit, il existe plusieurs façons d'obtenir un résultat comme ce que vous voulez.
Si vous savez à quoi va s'étendre le caractère générique,
et que ce n'est pas long, vous pouvez le générer avec l'expansion des accolades :
touch {red,orange,yellow,green,blue,indigo,violet}/rgb.txt
Une solution plus générale :
sh -c 'for arg do mkdir -- "$arg"/test; done' -- *
Gilles m'a aidé à trouver une autre façon
de le faire en bash.
benbradley=(*)
mkdir "${benbradley[@]/%//test}"
Évidemment benbradley
est juste un identifiant ici ; vous pouvez utiliser n'importe quel nom
(par exemple, n'importe quelle lettre).
Il m'a fallu quelques essais pour réussir, alors laissez-moi vous expliquer :
identifier=value
crée une variable (scalaire) nomméeidentifier
(si elle n'existe pas déjà) et attribue la valeurvalue
à cela.
Par exemple,G=Man
.
Vous pouvez référencer une variable scalaire avec$identifier
;
par exemple,$G
, ou, plus sûrement, comme${identifier}
.
Par exemple,$Gage
et$Gilla
peut être indéfini,
mais${G}age
estManage
et${G}illa
estManilla
.identifier=(value1 value2 … )
crée une variable tableau nomméeidentifier
(s'il n'existe pas déjà) et lui attribue les valeurs répertoriées.
Par exemple,
spectrum=(red orange yellow green blue indigo violet)
ou
all_text_files=(*.txt)
$name
fera référence au premier élément (et est donc assez inutile).
Vous pouvez référencer un élément arbitraire comme ${name[subscript]}
;
par exemple, ${spectrum[0]}
est red
et ${spectrum[4]}
est blue
.
Et vous pouvez utiliser ${name[@]}
et ${name[*]}
pour référencer le tableau entier.
Alors, le voici en morceaux :
" ${ benbradley[@] / % / /test } "
${parameter/pattern/string}
développe le${parameter}
et remplace la correspondance la plus longue
dupattern
avecstring
.- Si
pattern
commence par#
,
il doit correspondre au début de la valeur développée duparameter
.
Sipattern
commence par%
,
il doit correspondre à la fin de la valeur développée duparameter
.
En d'autres termes,
%(the-rest_of_the_pattern)
agit comme
(the-rest_of_the_regex)$
(oui, cela semble un peu en arrière).
Donc un pattern
c'est juste un %
est comme une expression régulière qui est juste un $
–
il correspond à la fin de la chaîne d'entrée (espace de recherche).
- Et j'utilise une
string
de/test
.
Cela remplace donc la fin du paramètre par/test
(c'est-à-dire qu'il ajoute/test
au paramètre). - Une autre chose à propos du
${parameter/pattern/string}
ce n'est pas intuitif, c'est qu'il toujours se termine par un}
.
Il n'est pas nécessaire, et ne peut pas , terminez par un troisième/
.
Par conséquent, un/
dansstring
ne peut pas être interprété comme un délimiteur,
et donc nous pouvons avoir unestring
de/test
sans avoir besoin d'échapper le/
. - Si
parameter
est une variable tableau
indicée par@
ou*
,
l'opération de substitution est appliquée tour à tour à chaque membre du tableau. - Lorsque vous référencez un tableau en tant que
${name[@]}
(plutôt que${name[*]}
) et placez les résultats entre guillemets,
vous préservez l'intégrité des éléments du tableau
(c'est-à-dire que vous préservez les espaces et autres caractères spéciaux)
sans combiner les éléments séparés en un long mot.