Comment puis-je imprimer des lignes à partir d'un fichier vers l'arrière (sans utiliser «tac»)?

26

Je veux imprimer des lignes d'un fichier en arrière sans utiliser de taccommande. Existe-t-il une autre solution pour faire une telle chose avec bash?

jimmij
la source
stackoverflow.com/questions/742466/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Réponses:

33

Utilisation sedpour émuler tac:

sed '1!G;h;$!d' ${inputfile}
Johnsyweb
la source
3
C'est une bonne solution, mais certaines explications sur la façon dont cela fonctionne seront encore meilleures.
Chen Levy
10
C'est un célèbre sedmonoplace. Voir "36. Ordre inverse des lignes (émuler la commande" tac "Unix)." dans Famous Sed One-Liners Explained pour une explication complète de son fonctionnement.
Johnsyweb
6
Peut-être intéressant de noter: "Ces deux lignes simples utilisent en fait beaucoup de mémoire car elles gardent le fichier entier dans le tampon de maintien dans l'ordre inverse avant de l'imprimer. Évitez ces lignes simples pour les fichiers volumineux."
M. Shickadance
Faites donc toutes les autres réponses (sauf peut-être celle qui utilise sort- il y a une chance qu'il utilise un fichier temporaire).
Random832
1
Notez que tac est plus rapide pour les fichiers normaux car il lit le fichier en arrière. Pour les tuyaux, il doit faire la même chose que les autres solutions (conserver en mémoire ou dans des fichiers temporaires), ce n'est donc pas beaucoup plus rapide.
Stéphane Chazelas
8

Avec ed:

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

Si vous êtes sur BSD/ OSX(et si tout va bien bientôt sur GNU/ linuxaussi car ce sera POSIX ):

tail -r infile
don_crissti
la source
6

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

via awk one liners

Mark McKinstry
la source
4
Un plus court:awk 'a=$0RS a{}END{printf a}'
ninjalj
@ninjalj: il peut être plus court, mais il devient extrêmement lent à mesure que la taille du fichier augmente. J'ai abandonné après avoir attendu 2min 30sec. but your first perl reverse <> `c'est la meilleure / la plus rapide réponse sur la page (pour moi), à 10 fois plus rapide que cette awkréponse (tous les awk anseres sont à peu près les mêmes, dans le temps)
Peter.O
1
Ouawk '{a[NR]=$0} END {while (NR) print a[NR--]}'
Stéphane Chazelas
5

Comme vous l'avez demandé en bash, voici une solution qui n'utilise pas awk, sed ou perl, juste une fonction bash:

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

La sortie de

echo 'a
b
c
d
' | reverse

est

d
c
b
a

Comme prévu.

Mais attention, les lignes sont stockées en mémoire, une ligne dans chaque instance appelée récursivement de la fonction. Si prudent avec les gros fichiers.

philfr
la source
1
Cela devient rapidement peu pratique à mesure que la taille du fichier augmente, par rapport à même les plus lentes des autres réponses, et comme vous l'avez suggéré, il souffle assez facilement la mémoire: * fonction récursive bash ... mais c'est une idée intéressante. Erreur de segmentation *
Peter.O
4

Vous pouvez le diriger à travers:

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

Les awkpréfixes de chaque ligne avec le numéro de ligne suivi d'un espace. L' sortinverse l'ordre des lignes en triant le premier champ (numéro de ligne) dans l'ordre inverse, style numérique. Et les sedbandes des numéros de ligne.

L'exemple suivant montre cela en action:

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'

Il génère:

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

la source
D'accord. Merci! Je vais essayer ça. Et merci pour les commentaires!
5
cat -nagit commeawk '{print NR" "$0}'
Chen Levy
2
Je pense que cela s'appelle transformer Schwartzian , ou décorer-trier-
décorer
Cette approche générale est intéressante dans la mesure où le tri gère l'utilisation de fichiers sur le disque si la tâche est trop volumineuse pour être raisonnablement effectuée en mémoire. Cela peut être plus doux en mémoire si vous avez utilisé des fichiers temporaires entre les étapes plutôt que des canaux.
mc0e
1

En perl:

cat <somefile> | perl -e 'while(<>) { push @a, $_; } foreach (reverse(@a)) { print; }'
Frederik Deweerdt
la source
2
ou le plus courtperl -e 'print reverse<>'
ninjalj
2
( En fait, il y a une plus courte, mais il est vraiment laid, témoin de son horreur: perl -pe '$\=$_.$\}{')
ninjalj
@Frederik Deweerdt: Rapide, mais il perd la première ligne ... @ ninjalj: reverse<)c'est rapide: bon! mais le "vraiment moche" est extrêmement lent car le nombre de lignes augmente. !! ....
Peter.O
@ Peter.O le -nétait superflu là-bas, merci.
Frederik Deweerdt
La bonne chose à propos de celui-ci est que le contenu du fichier n'est pas nécessairement lu en mémoire (sauf éventuellement en morceaux par sort).
Kusalananda
0

Solution BASH uniquement

lire le fichier dans le tableau bash (une ligne = un élément du tableau) et imprimer le tableau dans l'ordre inverse:

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
la source
Essayez-le avec des lignes en retrait ... La version de philfr est un peu meilleure, mais toujours très ludique, donc quand il s'agit de traitement de texte, ne l'utilisez jamais while..read.
don_crissti
Utilisez IFS=''et read -rpour empêcher toutes sortes de fuites et de retrait IFS de fuite de le visser. Je pense que le bash mapfile ARRAY_NAMEbuiltin est une meilleure solution pour lire dans les tableaux.
Arthur2e5
0

Bash, avec mapfilementionné dans les commentaires de fiximan, et en fait une version peut-être meilleure:

# 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
}

Ses performances sont fondamentalement comparables à la sedsolution et deviennent plus rapides à mesure que le nombre de lignes demandées diminue.

Arthur2e5
la source
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
la source
0
  • Numéroter les lignes avec nl
  • trier dans l'ordre inverse par numéro
  • supprimer les numéros avec sed

comme indiqué ici:

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

Résultat:

c
a
f
e
Utilisateur inconnu
la source
-3
awk '{print NR" "$0}' | sort -nr
Rock star
la source