Ich schreibe ein Skript, das die Anzahl der Zeichen in der Ausgabe eines Befehls in einem einzigen Schritt berechnen muss .
Beispielsweise sollte die Verwendung des Befehls zurückgegeben readlink -f /etc/fstab
werden, 10
da die Ausgabe dieses Befehls 10 Zeichen lang ist.
Dies ist bereits mit gespeicherten Variablen mit folgendem Code möglich:
variable="somestring";
echo ${#variable};
# 10
Leider funktioniert die Verwendung derselben Formel mit einer vom Befehl generierten Zeichenfolge nicht:
${#(readlink -f /etc/fstab)};
# bash: ${#(readlink -f /etc/fstab)}: bad substitution
Ich verstehe, dass dies möglich ist, indem zuerst die Ausgabe in einer Variablen gespeichert wird:
variable=$(readlink -f /etc/fstab);
echo ${#variable};
Aber ich möchte den zusätzlichen Schritt entfernen.
Ist das möglich? Die Kompatibilität mit der Almquist-Shell (sh) unter Verwendung nur eingebauter oder Standarddienstprogramme ist vorzuziehen.
readlink -f /etc/fstab
ist 11 Zeichen. Vergessen Sie nicht die Newline. Andernfalls würden Sie sehen,/etc/fstabluser@cern:~$
wann Sie es von einer Shell ausgeführt haben.Antworten:
Mit GNU Ausdruck :
Die
+
gibt es eine Besonderheit GNUexpr
um sicherzustellen , dass das nächste Argument als String behandelt wird , selbst wenn es sich um eine sein geschiehtexpr
Operator wiematch
,length
,+
...Das Obige entfernt alle nachfolgenden Zeilen der Ausgabe. Um es zu umgehen:
Das Ergebnis wurde von 2 subtrahiert, da der letzte Zeilenumbruch
readlink
und das von.
uns hinzugefügte Zeichen .Mit Unicode-Zeichenfolge
expr
scheint dies nicht zu funktionieren, da die Länge der Zeichenfolge in Byte anstelle der Anzahl der Zeichen zurückgegeben wird (siehe Zeile 654 ).Sie können also Folgendes verwenden:
POSIXLY:
Das Leerzeichen vor der Befehlssubstitution verhindert, dass der Befehl mit dem Zeichenfolgenanfang abstürzt.
-
Daher müssen wir 3 subtrahieren.quelle
LC_ALL=C.UTF-8
, was die Dinge erheblich vereinfacht, wenn die Codierung der Zeichenfolge nicht im Voraus bekannt ist.expr length $(echo "*")
- Nein. Verwenden Sie mindestens doppelte Anführungszeichen :expr length "$(…)"
. Dadurch werden nachgestellte Zeilenumbrüche aus dem Befehl entfernt. Dies ist eine unausweichliche Funktion der Befehlssubstitution. (Sie können esIch bin mir nicht sicher, wie ich das mit Shell-Buildins machen soll ( Gnouc ist es allerdings ), aber die Standard-Tools können helfen:
Sie können verwenden,
wc -m
welche Zeichen zählen. Leider zählt auch der letzte Zeilenumbruch, sodass Sie diesen zuerst entfernen müssen:Sie können natürlich verwenden
awk
Oder Perl
quelle
expr
ist ein eingebauter? In welcher Schale?Normalerweise mache ich das so:
Um Befehle auszuführen, würde ich es so anpassen:
Dieser Ansatz ähnelt dem, was Sie in Ihren beiden Schritten gemacht haben, außer dass wir sie zu einem einzigen Einzeiler kombinieren.
quelle
-m
anstelle von verwenden-c
. Mit Unicode-Zeichen wird Ihr Ansatz unterbrochen.readlink -f /etc/fstab | wc -m
?${#variable}
? Verwenden Sie mindestens doppelte Anführungszeichenecho -n "$variable"
, aber dies schlägt immer noch fehl, wenn z. B. der Wert vonvariable
ist-e
. Wenn Sie es in Kombination mit einer Befehlsersetzung verwenden, beachten Sie, dass nachfolgende Zeilenumbrüche entfernt werden.Sie können externe Dienstprogramme aufrufen (siehe andere Antworten), aber dadurch wird Ihr Skript langsamer, und es ist schwierig, die Installation richtig zu machen.
Zsh
In zsh können Sie schreiben
${#$(readlink -f /etc/fstab)}
, um die Länge der Befehlsersetzung zu ermitteln. Beachten Sie, dass dies nicht die Länge der Befehlsausgabe ist, sondern die Länge der Ausgabe ohne nachgestellte Zeilenumbruch.Wenn Sie die genaue Länge der Ausgabe wünschen, geben Sie am Ende ein zusätzliches Zeichen ohne Zeilenumbruch aus und subtrahieren Sie eines.
Wenn Sie die Nutzlast in der Ausgabe des Befehls wünschen, müssen Sie hier zwei subtrahieren , da die Ausgabe von
readlink -f
der kanonische Pfad plus eine neue Zeile ist.Dies unterscheidet sich von
${#$(readlink -f /etc/fstab)}
dem seltenen, aber möglichen Fall, in dem der kanonische Pfad selbst in einer neuen Zeile endet.Für dieses spezielle Beispiel benötigen Sie überhaupt kein externes Dienstprogramm, da zsh über ein integriertes Konstrukt verfügt, das
readlink -f
über den Verlaufsmodifikator äquivalent istA
.Verwenden Sie den Verlaufsmodifikator in einer Parametererweiterung, um die Länge zu ermitteln:
Wenn Sie den Dateinamen in einer Variablen
filename
haben, wäre das${#filename:A}
.Muscheln im Bourne / POSIX-Stil
Keine der reinen Bourne / POSIX-Shells (Bourne, Ash, Mksh, Ksh93, Bash, Yash…) hat eine ähnliche Erweiterung, die ich kenne. Wenn Sie eine Parametersubstitution auf die Ausgabe einer Befehlssubstitution anwenden oder Parametersubstitutionen verschachteln müssen, verwenden Sie aufeinanderfolgende Stufen.
Sie können die Verarbeitung in eine Funktion einfügen, wenn Sie möchten.
oder
aber es gibt normalerweise keinen Nutzen; Mit Ausnahme von ksh93 kann ein zusätzlicher Fork die Ausgabe der Funktion verwenden, sodass Ihr Skript langsamer wird und es selten Vorteile für die Lesbarkeit gibt.
Die Ausgabe von
readlink -f
ist wiederum der kanonische Pfad plus eine neue Zeile; Wenn Sie die Länge des kanonischen Pfades möchten, subtrahieren Sie 2 statt 1 Zollcommand_output_length
. Die Verwendungcommand_output_length_sans_trailing_newlines
liefert nur dann das richtige Ergebnis, wenn der kanonische Pfad selbst nicht in einer neuen Zeile endet.Bytes gegen Zeichen
${#…}
soll die Länge in Zeichen sein, nicht in Bytes, was bei Multibyte-Gebietsschemas einen Unterschied macht. Ziemlich aktuelle Versionen von ksh93, bash und zsh berechnen die Länge in Zeichen gemäß dem Wert vonLC_CTYPE
zum Zeitpunkt der${#…}
Erweiterung des Konstrukts. Viele andere gängige Shells unterstützen Multibyte-Gebietsschemas nicht wirklich: Ab Strich 0.5.7 geben mksh 46 und posh 0.12.3${#…}
die Länge in Bytes zurück. Wenn Sie die Länge in Zeichen zuverlässig festlegen möchten, verwenden Sie daswc
Dienstprogramm:Solange
$LC_CTYPE
ein gültiges Gebietsschema festgelegt ist, können Sie sicher sein, dass dies entweder fehlerhaft ist (auf einer alten oder eingeschränkten Plattform, die keine Multibyte-Gebietsschemas unterstützt) oder die richtige Länge in Zeichen zurückgibt. (Für Unicode bedeutet "Länge in Zeichen" die Anzahl der Codepunkte - die Anzahl der Glyphen ist aufgrund von Komplikationen wie dem Kombinieren von Zeichen eine weitere Geschichte.)Wenn Sie die Länge in Bytes möchten, legen Sie diese
LC_CTYPE=C
vorübergehend fest oder verwenden Siewc -c
stattdessenwc -m
.Das Zählen von Bytes oder Zeichen mit
wc
schließt alle nachfolgenden Zeilenumbrüche aus dem Befehl ein. Wenn Sie die Länge des kanonischen Pfads in Bytes angeben möchten, ist dies der FallSubtrahieren Sie 2, um es in Zeichen zu erhalten.
quelle
echo .
zwei Zeichen hinzu, aber das zweite Zeichen ist eine nachgestellte neue Zeile, die durch die Befehlsersetzung entfernt wird.readlink
Ausgabe plus der.
vonecho
. Wir sind uns einig, dassecho .
zwei Zeichen hinzugefügt werden, aber die nachfolgende neue Zeile wurde entfernt. Versuche es mitprintf .
oder sehen Sie meine Antwort unix.stackexchange.com/a/160499/38906 .readlink
ist das Link-Ziel plus eine neue Zeile.Dies funktioniert in
dash
, erfordert jedoch, dass die Zielvariable definitiv leer oder nicht gesetzt ist. Deshalb sind dies eigentlich zwei Befehle - ich leere$l
im ersten explizit :AUSGABE
Das sind alles Shell-Buildins - natürlich nicht einschließlich
readlink
-, aber wenn Sie es in der aktuellen Shell auf diese Weise auswerten, müssen Sie die Zuweisung%.s
vornehmen, bevor Sie die len erhalten. Deshalb verwende ich das erste Argument in der Formatzeichenfolgeprintf
und füge es einfach erneut hinzu der Literalwert am Ende derprintf
Arg-Liste.Mit
eval
:AUSGABE
Sie können sich der gleichen Sache nähern, aber anstelle der Ausgabe in einer Variablen im ersten Befehl erhalten Sie sie auf stdout:
... was schreibt ...
... zum Dateideskriptor 1, ohne vars in der aktuellen Shell einen Wert zuzuweisen.
quelle
variable=$(readlink -f /etc/fstab); echo ${#variable};
gespeichert wird. Ich möchte jedoch den zusätzlichen Schritt entfernen."expr
, zum Beispiel. Es ist wahrscheinlich nur wichtig, wenn das Erhalten des Len den Wert irgendwie verschließt, was ich zugeben muss, dass ich Schwierigkeiten habe zu verstehen, warum das sein kann, aber ich vermute, dass es einen Fall geben könnte, in dem es wichtig ist.eval
Art und Weise, nebenbei bemerkt , ist wahrscheinlich die sauberste hier - es weist den Ausgang und die len auf den gleichen var name in einer einzigen Ausführung - sehr nah zu tunl=length(l):out(l)
. Dadurchexpr length $(command)
hat den Wert für die len occlude, übrigens.