Ich schreibe ein Skript, das die Anzahl der Zeichen in der Ausgabe eines Befehls berechnen muss inein einziger Schritt.
Beispielsweise readlink -f /etc/fstab
sollte die Verwendung des Befehls zurückgeben 10
, da die Ausgabe dieses Befehls 10 Zeichen lang ist.
Dies ist mit gespeicherten Variablen bereits mit dem folgenden Code möglich:
variable="somestring";
echo ${#variable};
# 10
Leider funktioniert die Verwendung derselben Formel mit einer durch einen Befehl generierten Zeichenfolge nicht:
${#(readlink -f /etc/fstab)};
# bash: ${#(readlink -f /etc/fstab)}: bad substitution
Ich verstehe, dass dies möglich ist, indem man zuerst die Ausgabe in einer Variablen speichert:
variable=$(readlink -f /etc/fstab);
echo ${#variable};
Aber ich möchte den zusätzlichen Schritt entfernen.
Ist das möglich? Kompatibilität mit der Almquist-Shell (sh) unter Verwendung nur integrierter oder standardmäßiger Dienstprogramme ist vorzuziehen.
Antwort1
MitGNU-Ausdruck:
$ expr length + "$(readlink -f /etc/fstab)"
10
Es +
gibt eine spezielle Funktion von GNU, expr
um sicherzustellen, dass das nächste Argument als Zeichenfolge behandelt wird, selbst wenn es sich um einen expr
Operator wie match
, length
, +
… handelt.
Das Obige entfernt alle nachfolgenden Zeilenumbrüche aus der Ausgabe. So umgehen Sie das Problem:
$ expr length + "$(readlink -f /etc/fstab; printf .)" - 2
10
Das Ergebnis wurde abgezogen zu2wegen des letzten Zeilenumbruchs readlink
und des Zeichens, das .
wir hinzugefügt haben.
Bei Unicode-Strings expr
scheint dies nicht zu funktionieren, da die Länge des Strings in Bytes statt in der Anzahl der Zeichen zurückgegeben wird (sieheLinie 654)
$ LC_ALL=C.UTF-8 expr length ăaa
4
Sie können also Folgendes verwenden:
$ printf "ăaa" | LC_ALL=C.UTF-8 wc -m
3
POSIXLY:
$ expr " $(readlink -f /etc/fstab; printf .)" : ".*" - 3
10
Das Leerzeichen vor der Befehlsersetzung verhindert, dass der Befehl mit der Zeichenfolge abstürzt, die mit beginnt. -
Daher müssen wir 3 abziehen.
Antwort2
Ich bin nicht sicher, wie das mit integrierten Shell-Funktionen funktioniert (Gnouc ist allerdings), aber die Standardwerkzeuge können helfen:
Sie können
wc -m
which verwenden, um Zeichen zu zählen. Leider zählt es auch den letzten Zeilenumbruch, sodass Sie diesen zuerst entfernen müssen:readlink -f /etc/fstab | tr -d '\n' | wc -m
Sie können natürlich
awk
readlink -f /etc/fstab | awk '{print length($0)}'
Oder Perl
readlink -f /etc/fstab | perl -lne 'print length'
Antwort3
Normalerweise mache ich es so:
$ echo -n "$variable" | wc -m
10
Um Befehle auszuführen, würde ich es folgendermaßen anpassen:
$ echo -n "$(readlink -f /etc/fstab)" | wc -m
10
Dieser Ansatz ähnelt dem, was Sie in Ihren beiden Schritten getan haben, außer dass wir sie zu einer einzigen Zeile kombinieren.
Antwort4
Dies funktioniert, dash
erfordert aber, dass die Zielvariable definitiv leer oder nicht gesetzt ist. Deshalb ist dies eigentlichzweiBefehle - ich leere ausdrücklich $l
im ersten:
l=;printf '%.slen is %d and result is %s\n' \
"${l:=$(readlink -f /etc/fstab)}" "${#l}" "$l"
AUSGABE
len is 10 and result is /etc/fstab
Das sind alles integrierte Shell-Funktionen – readlink
natürlich nicht einschließlich –, aber wenn Sie es auf diese Weise in der aktuellen Shell auswerten, bedeutet das, dass Sie die Zuweisung durchführen müssen, bevor Sie die Länge abrufen. Deshalb %.s
lösche ich das erste Argument in der printf
Formatzeichenfolge und füge es einfach erneut für den Literalwert am Ende der printf
Argumentliste von hinzu.
Mit eval
:
l=$(readlink -f /etc/fstab) eval 'l=${#l}:$l'
printf %s\\n "$l"
AUSGABE
10:/etc/fstab
Sie können nahezu dasselbe erreichen, aber statt der Ausgabe in einer Variablen im ersten Befehl erhalten Sie sie auf stdout:
PS4='${#0}:$0' dash -cx '2>&1' "$(readlink -f /etc/fstab)"
...der schreibt...
10:/etc/fstab
...zum Dateideskriptor 1, ohne einer Variable in der aktuellen Shell einen Wert zuzuweisen.