En bash, ¿cómo cuento el número de líneas en una variable?

82

Tengo una variable que tiene una cadena almacenada y necesito verificar si tiene líneas:

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

pseudocódigo:

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

Esa es mi idea sobre cómo comprobarlo. echo $var | wc -lno funciona - siempre dice 1, aunque lo ha hecho 3.

echo -e no funciona tan bien.

krack krackerz
fuente

Respuestas:

108

Las cotizaciones importan.

echo "$var" | wc -l
Ignacio Vázquez-Abrams
fuente
3
Eso no son comillas alrededor del comando, son comillas alrededor de la sustitución del comando. Los pares de comillas no interferirán.
Ignacio Vazquez-Abrams
38
Esto tiene una sutileza. Una cadena vacía devolverá 1 ya que el eco en una cadena vacía imprime una nueva línea.
Andrew Nguyen
3
En otras palabras: if [ -z "$var" ]; then printf '%s\n' '0'; else printf '%s\n' "${var%$'\n'}" | wc -l; fi. Pruebe con var=(sin líneas) var='foo'y var=$'foo\n'(ambas una línea en el sentido * nix).
l0b0
2
Y sin embargo, otra manera de conseguir en una variable: LINE_COUNT=$(wc -l <<< "${var}")
lucifurious
4
@Tim echo -nsimplemente disminuirá el recuento en uno. @lucifurious echo $(wc -l <<< "${NONEXISTENTVAR}")todavía da 1, no 0
Julián
73

La respuesta aceptada y otras respuestas publicadas aquí no funcionan en el caso de una variable vacía (cadena vacía o indefinida).

Esto funciona:

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

Por ejemplo:

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
Julian
fuente
No puedo reproducir lo que suena como un error de @PolyTekPatrick, es decir, WHITESPACE_ONE=' 'en mi shell (bash) todavía funciona, informando correctamente una línea.
Julian
Tienes razón. Juré que lo probé, pero debo haber perdido las comillas dobles o haber escrito algo mal. De hecho, uno o más caracteres de espacio en blanco se cuentan como 1 línea como era de esperar. Eliminé mi comentario anterior para evitar confundir a la gente.
PolyTekPatrick
4
¡Perfecto! Esta debería ser la respuesta aceptada. Es la única solución hasta ahora que responde adecuadamente a la pregunta en todos los casos. Gracias por mostrar los casos de prueba para probarlo.
PolyTekPatrick
4
¡SI! Yo también creo que esta debería ser la respuesta aceptada.
Martin Joiner
1
Esto es correcto, pero se puede simplificar usando en printflugar de echo -n. Agregué esto como una respuesta alternativa, para incluir los resultados de las pruebas. Vea abajo.
Stilez
23

Otra forma de usar aquí cadenas en bash:

wc -l <<< "$var"

Como se menciona en este comentario , un vacío $varresultará en 1 línea en lugar de 0 líneas porque aquí las cadenas agregan un carácter de nueva línea en este caso ( explicación ).

hablador
fuente
1
Tuve que hacer esto para obtener la respuesta correcta: wc -l <<<"$(echo "$var")"(sí, todos los símbolos eran necesarios)
Nicolai S
@NicolaiS Entonces hiciste algo mal. ¿Cuál fue el contenido $var?
speakr
1
@NicolaiS Eso es correcto porque tu varcontiene una línea : no interpretaste el \ncon nada. Pon varias líneas en tu vary funcionará, por ejemplo, con var="foo<ENTER>bar<ENTER>baz"<ENTER>.
speakr
2
xxd <<< ''crea este hexdump 00000000: 0a. Por lo tanto, <<<(Here Strings) agrega un carácter de nueva línea a cualquier contenido.
MiniMax
1
@MiniMax Gracias, agregué su entrada a mi respuesta. Puede encontrar una explicación de este comportamiento aquí .
speakr
9

Puede sustituir "wc -l" por "wc -w" para contar el número de palabras en lugar de líneas. Esto no contará ninguna línea nueva y se puede usar para probar si los resultados originales están vacíos antes de continuar.

Christopher Brunsdon
fuente
wc -lsoluciones salidas 1 incluso si la variable de entrada está vacía, por wc -wlo que será mejor usarla.
Kadir
9

Nadie ha mencionado la expansión de parámetros, así que aquí hay un par de formas de usar bash puro.

Método 1

Elimine los caracteres que no sean de nueva línea, luego obtenga la longitud de la cadena + 1. Las comillas son importantes .

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

Método 2

Convierta a matriz, luego obtenga la longitud de la matriz. Para que esto funcione, no use comillas .

 set -f # disable glob (wildcard) expansion
 IFS=$'\n' # let's make sure we split on newline chars
 var=(${var})
 echo ${#var[@]}
haqk
fuente
2
El método 2 es más limpio y probablemente más rápido. Sin embargo, se basa en el IFS. Así que configúrelo IFS=$'\n'para asegurarse de que la variable se divida en nuevas líneas al expandirla en una matriz:IFS=$'\n'; var=(${var})
untore
Me gusta el Método 2 debido a su mínima sobrecarga: no hay comandos externos y ni siquiera uno integrado está a la vista. También es bastante legible.
DKroot
1
Sin embargo, ShellCheck se queja: github.com/koalaman/shellcheck/wiki/SC2206 . Está en algo. set -fes necesario para evitar una expansión glob no deseada. Intente utilizar el método 2 en var=$'*\n.*'.
DKroot
bonificación automática para bash interno puro sin externowc -l
nhed
7

Una versión más simple de la respuesta de @ Julian, que funciona para todas las cadenas, con o sin \ n final (sí cuenta un archivo que contiene solo un \ n final como vacío):

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

  • Devuelve cero: variable no definida, cadena vacía, cadena que contiene una nueva línea desnuda
  • Devuelve 1: cualquier línea no vacía, con o sin nueva línea final
  • etc

Salida:

# 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
fuente
+1. Pasar banderas a echo(como echo -n) no es estándar y puede dar resultados diferentes en diferentes implementaciones. Considerando que printfhace lo que queremos por defecto. Como beneficio adicional, el uso le printfahorra un proceso, ya que es un shell integrado. (Sugerencia: printf "$a" | wc -les más conciso y evita el uso innecesario de grep)
joshtch
@joshtch Bueno ... no. Como dijeron otros en esta discusión printf .... | wc -l, solo eliminará una línea adicional (la nueva línea) para que, en caso de una línea vacía, el resultado sea 0 . Correcto. Pero si pasamos 2 líneas, el resultado será 1, donde la misma variable pasada al printf ... | grep "^"devolverá correctamente 2. Además, usar directamente printf "$a"es muy peligroso, porque puede llevar a errores silenciosos si la cadena contiene accidentalmente caracteres como %s, %dy así on ... Lo mismo si la cadena comienza con un guión. En cambio, el parámetro 2 en printfse escapa automáticamente
Luke Savefrogs
3

Las respuestas más votadas fallan si un grep no devolvió resultados.

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

Esta es la forma incorrecta de hacerlo :

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

Allí nos dirá, hay 1 Wiggum en la lista, incluso si no hay ninguno.

En su lugar, debe hacer una verificación adicional para ver si la variable está vacía (-z como en "es cero"). Si grep no devolvió nada, la variable estará vacía.

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
fuente
2

Otro método para contar el número de líneas en una variable, suponiendo que haya verificado que se completó correctamente o que no está vacío, para eso solo marque $? después de la afectación del resultado de subcapa var -:

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

readarray | mapfile es un comando interno de bash que convierte el archivo de entrada, o aquí cadena en este caso, en una matriz basada en nuevas líneas.

-t flag evita almacenar nuevas líneas al final de las celdas de la matriz, útil para el uso posterior de valores almacenados

Las ventajas de este método son:

  • sin comando externo (wc, grep, ...)
  • sin subcapa (tubería)
  • sin problemas de IFS (restaurar después de la modificación, difícil de usar con un alcance limitado por comandos en comandos internos, ...)
adrenocromo
fuente
1

Para evitar el nombre de archivo en el comando "wc -l":

lines=$(< "$filename" wc -l)
echo "$lines"
usuario3503711
fuente