¿Cómo puedo imprimir líneas desde el archivo hacia atrás (sin usar "tac")?

Respuestas:

33

Usando sedpara emular tac:

sed '1!G;h;$!d' ${inputfile}
Johnsyweb
fuente
3
Esta es una buena solución, pero alguna explicación de cómo funciona será aún mejor.
Chen Levy
10
Es un famoso sedone-liner. Consulte "36. Orden inverso de líneas (emular" tac "comando Unix)". en el famoso Sed One-Liners explicado para una explicación completa de cómo funciona.
Johnsyweb
66
Quizás valga la pena señalar: "Estas dos líneas simples realmente usan mucha memoria porque mantienen todo el archivo en el búfer de retención en orden inverso antes de imprimirlo. Evite estas líneas cortas para archivos grandes".
Sr. Shickadance
Entonces, haga todas las otras respuestas (excepto tal vez la que usa sort, existe la posibilidad de que use un archivo temporal).
Random832
1
Tenga en cuenta que tac es más rápido para los archivos normales porque lee el archivo al revés. Para las tuberías, tiene que hacer lo mismo que las otras soluciones (en memoria o en archivos temporales), por lo que no es significativamente más rápido.
Stéphane Chazelas
8

Con ed:

ed -s infile <<IN
g/^/m0
,p
q
IN

Si está en BSD/ OSX(y con suerte pronto en GNU/ linuxtambién, ya que será POSIX ):

tail -r infile
don_crissti
fuente
6

awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--] }' file.txt

a través de awk one liners

Mark McKinstry
fuente
44
Una más corta:awk 'a=$0RS a{}END{printf a}'
ninjalj
@ninjalj: puede ser más corto, pero se vuelve extremadamente lento a medida que aumenta el tamaño del archivo. Me di por vencido después de esperar 2 minutos y 30 segundos. but your first perl reverse <> `es la respuesta mejor / más rápida en la página (para mí), 10 veces más rápido que esta awkrespuesta (todos los awk anseres son casi iguales, en cuanto al tiempo)
Peter.O
1
O bienawk '{a[NR]=$0} END {while (NR) print a[NR--]}'
Stéphane Chazelas
5

Como pediste hacerlo en bash, aquí hay una solución que no utiliza awk, sed o perl, solo una función bash:

reverse ()
{
    local line
    if IFS= read -r line
    then
        reverse
        printf '%s\n' "$line"
    fi
}

La salida de

echo 'a
b
c
d
' | reverse

es

d
c
b
a

Como se esperaba.

Pero tenga en cuenta que las líneas se almacenan en la memoria, una línea en cada instancia recursivamente llamada instancia de la función. Tan cuidadoso con los archivos grandes.

philfr
fuente
1
Rápidamente se vuelve imprácticamente lento a medida que aumenta el tamaño del archivo, en comparación con incluso la más lenta de las otras respuestas, y como ha sugerido, vuela la memoria con bastante facilidad: * función recursiva bash ... pero es una idea interesante. Falla de segmentación *
Peter.O
4

Puedes canalizarlo a través de:

awk '{print NR" "$0}' | sort -k1 -n -r | sed 's/^[^ ]* //g'

Los awkprefijos de cada línea con el número de línea seguido de un espacio. El sortinvierte el orden de las líneas de clasificación en el primer campo (número de línea) en orden inverso, estilo numérico. Y las sedtiras de los números de línea.

El siguiente ejemplo muestra esto en acción:

pax$ echo 'a
b
c
d
e
f
g
h
i
j
k
l' | awk '{print NR" "$0}' | sort -k1 -n -r | sed 's/^[^ ]* //g'

Produce:

l
k
j
i
h
g
f
e
d
c
b
a

fuente
Okay. ¡Gracias! Intentaré esto Y gracias por los comentarios!
55
cat -nactúa comoawk '{print NR" "$0}'
Chen Levy el
2
Creo que se llama transformación de Schwartzian , o decorar-ordenar-decorar
ninjalj
Este enfoque general es bueno en ese tipo de manejadores que usan archivos en el disco si la tarea es demasiado grande para hacerla razonablemente en la memoria. Sin embargo, podría ser más suave en la memoria si usa archivos temporales entre los pasos en lugar de tuberías.
mc0e
1

En perl:

cat <somefile> | perl -e 'while(<>) { push @a, $_; } foreach (reverse(@a)) { print; }'
Frederik Deweerdt
fuente
2
o el más cortoperl -e 'print reverse<>'
ninjalj
2
(En realidad, hay una más corta, pero es muy feo, testigo de su horror: perl -pe '$\=$_.$\}{')
ninjalj
@Frederik Deweerdt: Rápido, pero pierde la primera línea ... @ ninjalj: reverse<)es rápido: ¡bien! pero el "realmente feo" es extremadamente lento a medida que aumenta el número de líneas. !! ....
Peter.O
@ Peter.O -nfue superfluo allí, gracias.
Frederik Deweerdt
Lo bueno de este es que el contenido del archivo no se lee necesariamente en la memoria (excepto posiblemente en fragmentos por sort).
Kusalananda
0

Solución solo BASH

lea el archivo en la matriz bash (una línea = un elemento de la matriz) e imprima la matriz en orden inverso:

i=0 

while read line[$i] ; do
    i=$(($i+1))
done < FILE


for (( i=${#line[@]}-1 ; i>=0 ; i-- )) ; do
    echo ${line[$i]}
done
Fiximan
fuente
Pruébalo con líneas sangradas ... la versión de philfr es un poco mejor, pero sigue siendo muuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuucialidad, realmente cuando se trata de procesamiento de texto, nunca la use while..read.
don_crissti
Use IFS=''y read -rpara evitar que todo tipo de fugas y la eliminación de IFS final lo arruinen. Sin mapfile ARRAY_NAMEembargo, creo que bash builtin es una mejor solución para leer en matrices.
Arthur2e5
0

Bash, con los mapfilecomentarios mencionados en Fiximan, y en realidad una versión posiblemente mejor:

# last [LINES=50]
_last_flush(){ BUF=("${BUF[@]:$(($1-LINES)):$1}"); } # flush the lines, can be slow.
last(){
  local LINES="${1:-10}" BUF
  ((LINES)) || return 2
  mapfile -C _last_flush -c $(( (LINES<5000) ? 5000 : LINES+5 )) BUF
  BUF=("${BUF[@]}") # Make sure the array subscripts make sence, can be slow.
  ((LINES="${#BUF[@]}" > LINES ? LINES : "${#BUF[@]}"))
  for ((i="${#BUF[@]}"; i>"${#BUF[@]}"-LINES; i--)); do echo -n "${BUF[i]}"; done
}

Su rendimiento es básicamente comparable a la sedsolución, y se acelera a medida que disminuye el número de líneas solicitadas.

Arthur2e5
fuente
0
Simple do this with your file to sort the data in reverse order, and it should be unique.

sed -n '1h; 1d; G; h; $ p'

Bipul Kumar
fuente
0
  • Numera las líneas con nl
  • ordenar en orden inverso por número
  • eliminar los números con sed

como se muestra aquí:

echo 'e
> f
> a
> c
> ' | nl | sort -nr | sed -r 's/^ *[0-9]+\t//'

Resultado:

c
a
f
e
usuario desconocido
fuente
-3
awk '{print NR" "$0}' | sort -nr
Estrella de rock
fuente