Shells wie Bash und Zsh erweitern Platzhalter in Argumente, so viele Argumente wie dem Muster entsprechen:
$ echo *.txt
1.txt 2.txt 3.txt
Aber was ist, wenn ich nur die erste Übereinstimmung zurückgeben möchte, nicht alle Übereinstimmungen?
$ echo *.txt
1.txt
Ich habe nichts gegen Shell-spezifische Lösungen, aber ich möchte eine Lösung, die mit Leerzeichen in Dateinamen arbeitet.
Antworten:
Eine robuste Methode in bash besteht darin, in ein Array zu expandieren und nur das erste Element auszugeben:
(Sie können sogar nur
echo $files
einen fehlenden Index als [0] behandeln.)Dies behandelt sicher Leerzeichen, Tabulatoren, Zeilenumbrüche und andere Metazeichen, wenn die Dateinamen erweitert werden. Beachten Sie, dass die Gebietsschemaeinstellungen das ändern können, was "zuerst" ist.
Sie können dies auch interaktiv mit einer Bash- Vervollständigungsfunktion tun :
Dadurch wird die
_echo
Funktion an die Vervollständigung von Argumenten an denecho
Befehl gebunden (wobei die normale Vervollständigung außer Kraft gesetzt wird). Ein zusätzliches "*" wird im obigen Code angehängt. Sie können einfach auf einen Teil des Dateinamens tippen, und hoffentlich wird das Richtige passieren.Der Code ist leicht verworren, anstatt set oder assume
nullglob
(shopt -s nullglob
) zu verwenden. Wir überprüfencompgen -G
, ob der Globus auf einige Übereinstimmungen erweitert werden kann, erweitern ihn dann sicher zu einem Array und setzen schließlich COMPREPLY, damit das Zitieren robust ist.Sie können dies teilweise mit bash tun (programmgesteuert ein Glob erweitern)
compgen -G
, aber es ist nicht robust, da es ohne Anführungszeichen für stdout ausgibt.Wie üblich ist die Vervollständigung ziemlich kompliziert, was die Vervollständigung anderer Dinge, einschließlich Umgebungsvariablen, bricht ( Einzelheiten zum Emulieren des Standardverhaltens finden Sie in der
_bash_def_completion()
Funktion hier ).Sie können auch nur
compgen
außerhalb einer Vervollständigungsfunktion Folgendes verwenden:Ein Punkt, den Sie beachten sollten, ist, dass "~" kein Glob ist, sondern von bash in einer separaten Expansionsstufe behandelt wird, ebenso wie $ variables und andere Expansionen.
compgen -G
Führt nur das Verschieben von Dateinamen durch,compgen -W
gibt Ihnen jedoch die gesamte Standarderweiterung von bash wieder, obwohl möglicherweise zu viele Erweiterungen (einschließlich``
und$()
) vorhanden sind. Im Gegensatz zu-G
der-W
ist sicher zitiert (ich die Unterschiede nicht erklären kann). Da der Zweck von darin-W
besteht, dass es Token erweitert, bedeutet dies, dass es "a" zu "a" erweitert, auch wenn keine solche Datei vorhanden ist, so dass es möglicherweise nicht ideal ist.Dies ist einfacher zu verstehen, kann jedoch unerwünschte Nebenwirkungen haben:
Dann:
touch $'curious \n filename'
echo curious*
tabBeachten Sie die Verwendung von
printf %q
, um die Werte sicher anzugeben.Eine letzte Option ist die Verwendung einer durch 0 getrennten Ausgabe mit GNU-Dienstprogrammen (siehe bash FAQ ):
Diese Option gibt Ihnen ein wenig mehr Kontrolle über die Sortierreihenfolge (die Reihenfolge beim Erweitern eines Glob
LC_COLLATE
hängt von Ihrem Gebietsschema ab / und kann die Groß- / Kleinschreibung ändern), ist aber ansonsten ein ziemlich großer Hammer für ein so kleines Problem ;-)quelle
Verwenden Sie in zsh das
[1]
Glob-Qualifikationsmerkmal . Beachten Sie, dass dieser Sonderfall zwar höchstens eine Übereinstimmung zurückgibt, aber dennoch eine Liste ist und Globs nicht in Kontexten erweitert werden, die ein einzelnes Wort erwarten, z. B. Zuweisungen (außer Array-Zuweisungen).In ksh oder bash können Sie die gesamte Liste der Übereinstimmungen in ein Array einfügen und das erste Element verwenden.
In jeder Shell können Sie die Positionsparameter festlegen und den ersten verwenden.
Dadurch werden die Positionsparameter überlastet. Wenn Sie das nicht wollen, können Sie eine Unterschale verwenden.
Sie können auch eine Funktion verwenden, die über einen eigenen Satz von Positionsparametern verfügt.
quelle
*.txt([1,n])
Versuchen:
Hinweis: Die Dateinamenerweiterung wird nach der Sortierfolge sortiert, die für das aktuelle Gebietsschema gilt.
quelle
Eine einfache Lösung:
Oder verwenden
printf
Sie, wenn Sie es vorziehen.quelle
Ich bin gerade über diese alte Frage gestolpert, als ich mich das gleiche gefragt habe. Ich endete damit:
echo $(ls *.txt | head -n1)
Sie können natürlich
head
mittail
und-n1
mit jeder anderen Nummer ersetzen .Das oben Genannte funktioniert nicht, wenn Sie mit Dateien arbeiten, deren Name Zeilenumbrüche enthält. Zum Arbeiten mit Zeilenumbrüchen können Sie Folgendes verwenden:
ls -b *.txt | head -n1 | sed -E 's/\\n/\n/g'
(Funktioniert nicht bei BSD)ls -b *.txt | head -n1 | sed -e 's/\\n/\'$'\n/g'
ls -b *.txt | head -n1 | perl -pe 's/\\n/\n/g'
echo -e "$(ls -b *.txt | head -n1)"
(Funktioniert mit jedem Sonderzeichen)quelle
Ein Anwendungsfall, auf den ich häufig stoße, ist das Definieren des oberen / unteren Verzeichnisses nach der Glob-Erweiterung (z. B. ein Verzeichnis mit versionierten SDKs oder Build-Tools). In dieser Situation möchte ich diesen Verzeichnisnamen normalerweise in einer Variablen speichern, um ihn an einigen Stellen in einem Shell-Skript zu verwenden.
Dieser Befehl erledigt das normalerweise für mich:
Haftungsausschluss: Die Glob-Erweiterung sortiert Ihre Ordner nicht nach Semver. du wurdest gewarnt. Dies ist großartig, wenn Sie
Dockerfile
nur eine Version eines Verzeichnisses haben, diese Verzeichnisversion jedoch von Bild zu Bild variieren kann 🤷quelle
mkdir "$(echo one; echo two)"
und sehen Sie, was ich meine.tail
?dirname
akzeptiert nur einen einzelnen Pfadnamen. Sie können sich also nicht darauf verlassen, dass dieser für mehrere Pfadnamen verwendet wird, es sei denn, Sie wissen, dass Ihre spezielle Implementierung dies unterstützt.