Cómo imprimir el número de caracteres en cada línea de un archivo de texto

82

Me gustaría imprimir el número de caracteres en cada línea de un archivo de texto usando un comando de Unix. Sé que es simple con powershell

gc abc.txt | % {$_.length}

pero necesito el comando Unix.

vikas368
fuente

Respuestas:

152

Utilice Awk.

awk '{ print length }' abc.txt
Fred Foo
fuente
2
¡Esto es varios órdenes de magnitud más rápido que aplicar wc -c a cada línea!
aerijman
@aerijman para este tipo de problemas, el número de creaciones de procesos suele ser lo que marca la mayor diferencia de rendimiento.
MarcH
Si una línea en el archivo contiene emojis, esto no producirá la longitud esperada.
user5507535
@ user5507535, depende de qué “longitud” espere realmente. Hay muchas definiciones posibles para Unicode (mawk usa bytes, no marcó gawk).
Jan Hudec
16
while IFS= read -r line; do echo ${#line}; done < abc.txt

Es POSIX, por lo que debería funcionar en todas partes.

Editar: Se agregó -r como lo sugirió William.

Editar: tenga cuidado con el manejo de Unicode. Bash y zsh, con la configuración regional correctamente establecida, mostrarán el número de puntos de código, pero el guión mostrará bytes, por lo que debe verificar lo que hace su shell. Y luego hay muchas otras posibles definiciones de longitud en Unicode de todos modos, por lo que depende de lo que realmente desee.

Editar: prefijo con IFS=para evitar perder espacios iniciales y finales.

Jan Hudec
fuente
+1, pero ... esto fallará si la entrada contiene '\'. Utilice read -r
William Pursell
Si una línea en el archivo contiene emojis, esto no producirá la longitud esperada.
user5507535
@ user5507535, en realidad, depende de la "longitud" que espere. Hay muchas definiciones posibles para Unicode (pero en este caso, diferentes shells harán cosas diferentes).
Jan Hudec
Siempre configure IFS=el readcomando cuando desee leer datos arbitrarios. Entonces IFS= read -r. readusa el IFSpara dividir palabras, y aunque todas las palabras divididas se vuelven a pegar en la única variable disponible ( line), no hay garantía de que se vuelvan a pegar con todos los caracteres separadores originales que tenían o solo uno potencialmente diferente unos. Por ejemplo, con el IFS predeterminado, la línea foo barpodría volverse foo bar, perdiendo 7 espacios. (Como cómo Stack Overflow perdió los espacios adyacentes en esa cadena de ejemplo en este comentario).
mtraceur
@mtraceur, la documentación dice explícitamente que "las palabras restantes y sus delimitadores intermedios se asignan al apellido", por lo que se vuelven a pegar junto con el separador original. Eso, sin embargo, no toma el cuidado de los líderes y se arrastran delimitadores, que son de hecho perdieron. Así que tienes razón, IFSdebería establecerse, pero el problema cuando no lo es es más sutil.
Jan Hudec
4

Probé las otras respuestas enumeradas anteriormente, pero están muy lejos de ser soluciones decentes cuando se trata de archivos grandes, especialmente una vez que el tamaño de una sola línea ocupa más de ~ 1/4 de la RAM disponible.

Tanto bash como awk absorben toda la línea, aunque para este problema no es necesario. Bash generará un error una vez que una línea sea demasiado larga, incluso si tiene suficiente memoria.

Implementé una secuencia de comandos de Python extremadamente simple y bastante no optimizada que, cuando se probó con archivos grandes (~ 4 GB por línea), no absorbió y es, con mucho, una mejor solución que las que se ofrecen.

Si este es un código crítico en el tiempo para la producción, puede reescribir las ideas en C o realizar mejores optimizaciones en la llamada de lectura (en lugar de leer solo un byte a la vez), después de probar que se trata de un cuello de botella.

El código asume que la nueva línea es un carácter de salto de línea, lo cual es una buena suposición para Unix, pero YMMV en Mac OS / Windows. Asegúrese de que el archivo termine con un salto de línea para asegurarse de que no se pase por alto el recuento de caracteres de la última línea.

from sys import stdin, exit

counter = 0
while True:
    byte = stdin.buffer.read(1)
    counter += 1
    if not byte:
        exit()
    if byte == b'\x0a':
        print(counter-1)
        counter = 0
Samuel Liew
fuente
1
La pregunta era para un archivo de "texto". No creo que 4 GB por línea se ajusten a una definición razonable de un archivo de texto.
MarcH
3

Aquí hay un ejemplo usando xargs:

$ xargs -d '\n' -I% sh -c 'echo % | wc -c' < file
Kenorb
fuente
Este "echo%" no maneja caracteres inseguros que necesitan citarse desde el shell. Además, "xargs" dividirá su archivo por espacios y líneas nuevas, no solo líneas nuevas como solicitó el póster original.
bovino
1

Prueba esto:

while read line    
do    
    echo -e |wc -m      
done <abc.txt    
Rahul
fuente
¿Querías decir echo -e | wc -m, no? Es un uso inútil de comandos; shell puede contar caracteres en una variable. Plus echo -ees totalmente incompatible y funciona en la mitad de los proyectiles, mientras que comenzar con alguna secuencia de escape funciona en otro y nada en el resto.
Jan Hudec