Wie zähle ich in bash die Anzahl der Zeilen in einer Variablen?

79

Ich habe eine Variable, in der eine Zeichenfolge gespeichert ist, und muss überprüfen, ob sie Zeilen enthält:

var=`ls "$sdir" | grep "$input"`

Pseudocode:

while [ ! $var's number of lines -eq 1 ]
  do something

Das ist meine Idee, wie ich es überprüfen soll. echo $var | wc -lfunktioniert nicht - es heißt immer 1, obwohl es hat 3.

echo -e funktioniert nicht so gut.

krack krackerz
quelle

Antworten:

103

Zitate sind wichtig.

echo "$var" | wc -l
Ignacio Vazquez-Abrams
quelle
3
Das sind keine Anführungszeichen rund um den Befehl, das sind Anführungszeichen rund um die Befehlsersetzung. Die Anführungszeichenpaare stören nicht.
Ignacio Vazquez-Abrams
37
Das hat eine Subtilität. Eine leere Zeichenfolge gibt 1 zurück, da das Echo einer leeren Zeichenfolge eine neue Zeile druckt.
Andrew Nguyen
3
Mit anderen Worten : if [ -z "$var" ]; then printf '%s\n' '0'; else printf '%s\n' "${var%$'\n'}" | wc -l; fi. Versuchen Sie es mit var=(keine Zeilen) var='foo'und var=$'foo\n'(beide eine Zeile im Sinne von * nix).
10b0
2
Und noch ein anderer Weg, um es in eine Variable zu bringen: LINE_COUNT=$(wc -l <<< "${var}")
lucifurious
4
@Tim echo -nverringert einfach die Anzahl um eins. @ Lucifurious echo $(wc -l <<< "${NONEXISTENTVAR}")gibt immer noch 1, nicht 0
Julian
72

Die akzeptierte Antwort und andere hier veröffentlichte Antworten funktionieren bei einer leeren Variablen (undefinierte oder leere Zeichenfolge) nicht.

Das funktioniert:

echo -n "$VARIABLE" | grep -c '^'

Zum Beispiel:

ZERO=
ONE="just one line"
TWO="first
> second"

echo -n "$ZERO" | grep -c '^'
0
echo -n "$ONE" | grep -c '^'
1
echo -n "$TWO" | grep -c '^'
2
julianisch
quelle
Ich kann nicht reproduzieren, was wie ein Fehler von @PolyTekPatrick klingt, dh WHITESPACE_ONE=' 'in meiner Shell (Bash) funktioniert es immer noch, eine Zeile korrekt zu melden.
Julian
Du hast recht. Ich habe geschworen, es getestet zu haben, aber ich muss die doppelten Anführungszeichen verpasst oder etwas falsch geschrieben haben. Ein oder mehrere Leerzeichen werden tatsächlich wie erwartet als eine Zeile gezählt. Ich habe meinen früheren Kommentar gelöscht, um Verwirrung zu vermeiden.
PolyTekPatrick
4
Perfekt! Dies sollte die akzeptierte Antwort sein. Dies ist die einzige Lösung, die die Frage in allen Fällen richtig beantwortet. Vielen Dank, dass Sie die Testfälle gezeigt haben, um dies zu beweisen.
PolyTekPatrick
4
JA! Ich denke auch, dass dies die akzeptierte Antwort sein sollte.
Martin Joiner
1
Dies ist richtig, kann aber printfeher mit als vereinfacht werden echo -n. Ich habe dies als alternative Antwort hinzugefügt, um Testergebnisse aufzunehmen. Siehe unten.
Stilez
23

Eine andere Möglichkeit, hier Zeichenfolgen in Bash zu verwenden:

wc -l <<< "$var"

Wie in diesem Kommentar erwähnt , führt ein Leerzeichen zu $var1 Zeile anstelle von 0 Zeilen, da hier Zeichenfolgen in diesem Fall ein Zeilenumbruchzeichen hinzufügen ( Erläuterung ).

speakr
quelle
1
Ich musste dies tun, um die richtige Antwort zu erhalten: wc -l <<<"$(echo "$var")"(Ja, jedes einzelne Symbol war notwendig)
Nicolai S
@NicolaiS Dann hast du etwas falsch gemacht. Was war der Inhalt von $var?
Speakr
1
@NicolaiS Das ist richtig, weil dein eine Zeilevar enthält : Du hast das mit nichts interpretiert . Fügen Sie mehrere Zeilen in Ihre ein und es wird funktionieren, z . B. mit . \nvarvar="foo<ENTER>bar<ENTER>baz"<ENTER>
Speakr
2
xxd <<< ''Erstelle diesen Hexdump 00000000: 0a. <<<Fügen Sie daher (Here Strings) jedem Inhalt ein Zeilenumbruchzeichen hinzu.
MiniMax
1
@MiniMax Danke, ich habe deine Eingabe zu meiner Antwort hinzugefügt. Eine Erklärung für dieses Verhalten finden Sie hier .
Speakr
9

Sie können "wc -l" durch "wc -w" ersetzen, um die Anzahl der Wörter anstelle von Zeilen zu zählen. Dies zählt keine neuen Zeilen und kann verwendet werden, um zu testen, ob Ihre ursprünglichen Ergebnisse leer sind, bevor Sie fortfahren.

Christopher Brunsdon
quelle
wc -lLösungen gibt 1 aus, auch wenn die Eingabevariable leer ist. Daher ist wc -wes besser, sie zu verwenden.
Kadir
8

Niemand hat die Parametererweiterung erwähnt, daher gibt es hier einige Möglichkeiten, Pure Bash zu verwenden.

Methode 1

Entfernen Sie nicht neue Zeilen und erhalten Sie dann die Zeichenfolgenlänge + 1. Anführungszeichen sind wichtig .

 var="${var//[!$'\n']/}"
 echo $((${#var} + 1))

Methode 2

In Array konvertieren, dann Array-Länge abrufen. Verwenden Sie keine Anführungszeichen, damit dies funktioniert .

 set -f # disable glob (wildcard) expansion
 IFS=$'\n' # let's make sure we split on newline chars
 var=(${var})
 echo ${#var[@]}
haqk
quelle
2
Methode 2 ist sauberer und wahrscheinlich schneller. Wie auch immer es sich auf die IFS. So setzt , IFS=$'\n'um sicherzustellen , dass der Variable auf neue Zeilen aufgeteilt wird , wenn es in ein Array erweitert:IFS=$'\n'; var=(${var})
untore
Ich mag Methode 2 wegen ihres minimalen Overheads: Es sind keine externen Befehle und nicht einmal ein eingebauter in Sicht. Es ist auch gut lesbar.
DKroot
1
ShellCheck beschwert sich jedoch: github.com/koalaman/shellcheck/wiki/SC2206 . Es ist auf etwas. set -fwird benötigt, um unerwünschte Glob-Expansion zu vermeiden. Versuchen Sie, Methode 2 anzuwenden var=$'*\n.*'.
DKroot
7

Eine einfachere Version von @ Julians Antwort, die für alle Zeichenfolgen mit oder ohne Trailing funktioniert \ n (eine Datei mit nur einem Trailing \ n wird als leer gezählt):

printf "%s" "$a" | grep -c "^"

  • Gibt Null zurück: nicht gesetzte Variable, leere Zeichenfolge, Zeichenfolge mit bloßem Zeilenumbruch
  • Rückgabe 1: Jede nicht leere Zeile mit oder ohne nachgestellte Zeilenumbruch
  • etc

Ausgabe:

# a=
# printf "%s" "$a" | grep -c "^"
0

# a=""
# printf "%s" "$a" | grep -c "^"
0

# a="$(printf "")"
# printf "%s" "$a" | grep -c "^"
0

# a="$(printf "\n")"
# printf "%s" "$a" | grep -c "^"
0

# a="$(printf " \n")"
# printf "%s" "$a" | grep -c "^"
1

# a="$(printf " ")"
# printf "%s" "$a" | grep -c "^"
1

# a="aaa"
# printf "%s" "$a" | grep -c "^"
1

# a="$(printf "%s" "aaa")"
# printf "%s" "$a" | grep -c "^"
1

# a="$(printf "%s\n" "aaa")"
# printf "%s" "$a" | grep -c "^"
1

# a="$(printf "%s\n%s" "aaa" "bbb")"
# printf "%s" "$a" | grep -c "^"
2

# a="$(printf "%s\n%s\n" "aaa" "bbb")"
# printf "%s" "$a" | grep -c "^"
2
Stilez
quelle
+1. Das Übergeben von Flags an echo(z. B. echo -n) ist kein Standard und kann bei verschiedenen Implementierungen zu unterschiedlichen Ergebnissen führen. Während printfmacht, was wir standardmäßig wollen. Als Bonus printferspart Ihnen die Verwendung einen Prozess, da es sich um eine integrierte Shell handelt. (Vorschlag: printf "$a" | wc -list prägnanter und vermeidet unnötige Verwendung von grep)
Joshtch
@ Joshtch Nun .. nein. Wie von anderen in dieser Diskussion gesagt, printf .... | wc -lwird nur eine zusätzliche Zeile (die neue Zeile) entfernt, so dass im Falle einer leeren Zeile das Ergebnis 0 ist . Richtig. Aber wenn wir zwei Linien passieren, wird das Ergebnis 1, wo die gleiche Variable auf den übergebenen printf ... | grep "^"korrekt zurück 2. Außerdem direkt mit printf "$a"ist sehr gefährlich, weil es zu stillen Fehlern führen kann , wenn die Zeichenfolge versehentlich Zeichen enthält wie %s, %dund so on ... Gleich, wenn die Zeichenfolge mit einem Bindestrich beginnt. Stattdessen wird der 2-Parameter in printfautomatisch
maskiert
3

Die am besten bewerteten Antworten schlagen fehl, wenn keine Ergebnisse von einem Grep zurückgegeben wurden.

Homer Simpson
Marge Simpson
Bart Simpson
Lisa Simpson
Ned Flanders
Rod Flanders
Todd Flanders
Moe Szyslak

Dies ist der falsche Weg :

wiggums=$(grep -iF "Wiggum" characters.txt);
num_wiggums=$(echo "$wiggums" | wc -l);
echo "There are ${num_wiggums} here!";

Es wird uns sagen, dass 1 Wiggum in der Liste ist, auch wenn es keine gibt.

Stattdessen müssen Sie eine zusätzliche Überprüfung durchführen, um festzustellen, ob die Variable leer ist ( -zwie in "ist Null"). Wenn grep nichts zurückgegeben hat, ist die Variable leer.

matches=$(grep -iF "VanHouten" characters.txt);

if [ -z "$matches" ]; then
    num_matches=0;
else
    num_matches=$(echo "$matches" | wc -l);
fi

echo "There are ${num_matches} VanHoutens on the list";
Jonathan Matthews
quelle
2

Eine andere Methode, um die Anzahl der Zeilen in einer Variablen zu zählen - vorausgesetzt, Sie haben überprüft, ob sie erfolgreich gefüllt wurde oder nicht leer ist. Überprüfen Sie dazu einfach $? nach var Subshell Ergebnisbeeinflussung -:

readarray -t tab <<<"${var}"
echo ${#tab[@]}

readarray | mapfile ist ein interner Bash-Befehl, der die Eingabedatei oder in diesem Fall die Zeichenfolge in ein Array konvertiert, das auf Zeilenumbrüchen basiert.

Das Flag -t verhindert das Speichern von Zeilenumbrüchen am Ende der Zellen des Arrays, was für die spätere Verwendung gespeicherter Werte nützlich ist

Vorteile dieser Methode sind:

  • kein externer Befehl (wc, grep, ...)
  • keine Unterschale (Rohr)
  • Keine IFS-Probleme (Wiederherstellung nach Änderung, schwierig mit befehlsbeschränktem Gültigkeitsbereich für interne Befehle zu verwenden, ...)
Adrenochrom
quelle
1

So vermeiden Sie Dateinamen im Befehl "wc -l":

lines=$(< "$filename" wc -l)
echo "$lines"
user3503711
quelle