Machen Sie also so etwas in bash
und die meisten anderen Shells funktionieren nicht, um mehrere Unterverzeichnisse oder Dateien in Unterverzeichnissen zu erstellen …
mkdir */test
touch */hello.txt
Es gibt natürlich viele Möglichkeiten, dies tatsächlich zu tun, meine bevorzugte ist die Verwendung von find
wenn möglich, anstatt einen for
zu verwenden Schleife, hauptsächlich für die Lesbarkeit.
Aber meine Frage ist, warum das obige nicht funktioniert?
Soweit ich weiß, liegt es daran, dass die vollständige Zieldatei/der vollständige Zielpfad nicht existiert, aber das ist sicherlich eine gute Sache, wenn ich versuche, mkdir
oder touch
. Ich bin immer einfach weitergegangen und habe es nie wirklich in Frage gestellt.
Aber hat jemand eine anständige Erklärung dafür, die mir hilft, es ein für alle Mal zu verstehen?
Akzeptierte Antwort:
Der Punkt, den Sie möglicherweise übersehen – mit dem viele Leute Probleme haben,
insbesondere wenn sie Erfahrung mit anderen Betriebssystemen haben, bevor sie zu *nix
kommen – ist, dass in vielen anderen Betriebssystemen Platzhalter auf der Befehlszeile werden normalerweise an den Befehl übergeben nach eigenem Gutdünken verarbeiten. Beispielsweise in der Windows-Eingabeaufforderung
rename *.jpeg *.jpg
Wohingegen in *nix, um die Arbeit
des/der Programmierer(s) der einzelnen Befehle zu vereinfachen (z.B. mv
), Wildcards
(wenn sie nicht in Anführungszeichen oder Escapezeichen gesetzt sind) werden von der Shell behandelt,
in einer Weise, die unabhängig von der Schnittstelle und Funktionalität
des Befehls ist, für den die Wildcards Argumente sind.
Die meisten Programme, die Dateinamen als Befehlszeilenargumente verwenden, erwarten, dass diese Dateien existieren
(ja, mkdir
und touch
sind Ausnahmen von dieser Regel,
ebenso wie mkfifo
, mknod
, und teilweise cp
, ln
, mv
, und rename
;
und wahrscheinlich gibt es noch andere),
also macht es für die Shell keinen Sinn, Platzhalter
zu Namen für nicht existierende Dateien zu erweitern.
Und für die Shell (und damit meine ich jeden Shell –
Bourne, bash, csh, fish, ksh, zsh, etc…), die Ausnahmen anders zu handhaben
wäre wahrscheinlich zu umständlich.
Das heißt, es gibt ein paar Möglichkeiten, um ein Ergebnis zu erzielen, das Ihren Wünschen entspricht.
Wenn Sie wissen, worauf der Platzhalter erweitert wird,
und es nicht lange dauert, können Sie ihn mit der geschweiften Erweiterung generieren:
touch {red,orange,yellow,green,blue,indigo,violet}/rgb.txt
Eine allgemeinere Lösung:
sh -c 'for arg do mkdir -- "$arg"/test; done' -- *
Gilles hat mir geholfen, einen anderen Weg
in Bash zu finden.
benbradley=(*)
mkdir "${benbradley[@]/%//test}"
Offensichtlich benbradley
ist hier nur eine Kennung; Sie können einen beliebigen Namen
verwenden (z. B. einen einzelnen Buchstaben).
Ich habe ein paar Versuche gebraucht, um das richtig hinzubekommen, also lassen Sie es mich aufschlüsseln:
identifier=value
erstellt eine (skalare) Variable namensidentifier
(sofern noch nicht vorhanden) und weist den Wertvalue
dazu.
Zum BeispielG=Man
.
Sie können eine skalare Variable mit$identifier
;
zum Beispiel$G
, oder sicherer als${identifier}
.
Zum Beispiel$Gage
und$Gilla
kann undefiniert sein,
aber${G}age
istManage
und${G}illa
istManilla
.identifier=(value1 value2 … )
erstellt eine Array-Variable mit dem Namenidentifier
(sofern noch nicht vorhanden) und weist ihm die aufgelisteten Werte zu.
Zum Beispiel
spectrum=(red orange yellow green blue indigo violet)
oder
all_text_files=(*.txt)
$name
wird auf das erste Element verweisen (und ist daher ziemlich nutzlos).
Sie können auf ein beliebiges Element als ${name[subscript]}
;
zum Beispiel ${spectrum[0]}
ist red
und ${spectrum[4]}
ist blue
.
Und Sie können ${name[@]}
und ${name[*]}
um auf das gesamte Array zu verweisen.
Also, hier ist es in Stücken:
" ${ benbradley[@] / % / /test } "
${parameter/pattern/string}
erweitert${parameter}
und ersetzt die längste Übereinstimmung
vonpattern
mitstring
.- Wenn
pattern
beginnt mit#
,
muss am Anfang des erweiterten Werts vonparameter
.
Wennpattern
beginnt mit%
,
muss am Ende des erweiterten Werts vonparameter
.
Mit anderen Worten
%(the-rest_of_the_pattern)
verhält sich wie
(the-rest_of_the_regex)$
(ja, das scheint ein wenig rückständig).
Also ein pattern
das ist nur ein %
ist wie ein regulärer Ausdruck, der nur ein $
ist –
entspricht dem Ende der Eingabezeichenfolge (Suchraum).
- Und ich verwende einen
string
von/test
.
Das ersetzt also das Ende des Parameters durch/test
(d. h. es wird/test
angehängt zum Parameter). - Noch etwas zu
${parameter/pattern/string}
nicht intuitiv ist, dass es immer ist endet mit einem}
.
Es muss nicht und kann nicht , enden mit einem dritten/
.
Also ein/
instring
kann nicht als Trennzeichen interpretiert werden,
und daher können wir einenstring
haben von/test
ohne dass/
maskiert werden muss . - Wenn
parameter
ist eine Array-Variable
, die mit@
tiefgestellt ist oder*
,
wird die Substitutionsoperation der Reihe nach auf jedes Mitglied des Arrays angewendet. - Wenn Sie auf ein Array als
${name[@]}
(anstelle von${name[*]}
) und setzen Sie die Ergebnisse in doppelte Anführungszeichen,
bewahren Sie die Integrität der Elemente des Arrays
(d. h. Sie behalten Leerzeichen und andere Sonderzeichen darin bei)
ohne die einzelnen Elemente zu kombinieren ein langes Wort.