Ordina un file di testo per lunghezza della linea inclusi gli spazi


137

Ho un file CSV che assomiglia a questo

AS2345, ASDF1232, Mr. Plain Esempio, 110 Binary ave., Atlantis, RI, 12345, (999) 123-5555,1,56
AS2345, ASDF1232, Mrs. Plain Example, 1121110 Ternary st. 110 Binary ave .., Atlantis, RI, 12345, (999) 123-5555,1.56
AS2345, ASDF1232, Mr. Plain Esempio, 110 Binary ave., Liberty City, RI, 12345, (999) 123-5555,1,56
AS2345, ASDF1232, Mr. Plain Esempio, 110 Ternary ave., Some City, RI, 12345, (999) 123-5555,1,56

Ho bisogno di ordinarlo per lunghezza della linea compresi gli spazi. Il seguente comando non include gli spazi, c'è un modo per modificarlo in modo che funzioni per me?

cat $@ | awk '{ print length, $0 }' | sort -n | awk '{$1=""; print $0}'

21
Mi piacerebbe molto vivere in Binary Avenue o Ternary Street, quelle persone sicuramente sarebbero d'accordo con cose come "8192 è un numero tondo"
schnaader

Risposte:


224

Risposta

cat testfile | awk '{ print length, $0 }' | sort -n -s | cut -d" " -f2-

Oppure, per eseguire il tuo sotto-ordinamento originale (forse non intenzionale) di qualsiasi linea di uguale lunghezza:

cat testfile | awk '{ print length, $0 }' | sort -n | cut -d" " -f2-

In entrambi i casi, abbiamo risolto il problema dichiarato allontanandoci da awk per il tuo taglio finale.

Linee di lunghezza corrispondente: cosa fare in caso di pareggio:

La domanda non specificava se si desiderasse o meno un ulteriore ordinamento per le linee di lunghezza corrispondente. Ho supposto che ciò sia indesiderato e ho suggerito l'uso di -s( --stable) per impedire che tali linee vengano ordinate l'una contro l'altra e mantenerle nell'ordine relativo in cui si verificano nell'input.

(Coloro che desiderano un maggiore controllo sull'ordinamento di questi legami potrebbero guardare --keyall'opzione sort .)

Perché la soluzione tentata della domanda ha esito negativo (ricostruzione di riga awk):

È interessante notare la differenza tra:

echo "hello   awk   world" | awk '{print}'
echo "hello   awk   world" | awk '{$1="hello"; print}'

Rendono rispettivamente

hello   awk   world
hello awk world

La sezione pertinente del manuale di (gawk) menziona solo a parte che awk ricostruirà l'intero $ 0 (basato sul separatore, ecc.) Quando cambi un campo. Immagino che non sia un comportamento folle. Ha questo:

"Infine, ci sono momenti in cui è conveniente forzare Awk a ricostruire l'intero record, usando il valore corrente dei campi e OFS. Per fare questo, usa l'assegnazione apparentemente innocua:"

 $1 = $1   # force record to be reconstituted
 print $0  # or whatever else with $0

"Questo costringe Awk a ricostruire il disco."

Test input che include alcune linee di uguale lunghezza:

aa A line   with     MORE    spaces
bb The very longest line in the file
ccb
9   dd equal len.  Orig pos = 1
500 dd equal len.  Orig pos = 2
ccz
cca
ee A line with  some       spaces
1   dd equal len.  Orig pos = 3
ff
5   dd equal len.  Orig pos = 4
g

1
heemayl, sì lo è, grazie. Ho cercato di abbinare la forma della tentata soluzione di OP, ove possibile, per consentirgli di concentrarsi solo sulle differenze importanti tra la sua e la mia.
Neillb

1
Vale la pena sottolineare che anche questo cat $@è rotto. Sicuramente lo vuoi assolutamente citare, tipocat "$@"
tripleee,

27

La soluzione AWK di Neillb è fantastica se vuoi davvero usarla awke spiega perché è una seccatura lì, ma se quello che vuoi è svolgere il lavoro rapidamente e non preoccuparti di ciò che fai, una soluzione è usare La sort()funzione di Perl con una routine caparison personalizzata per scorrere le righe di input. Ecco una fodera:

perl -e 'print sort { length($a) <=> length($b) } <>'

Puoi metterlo nella tua pipeline ovunque tu ne abbia bisogno, ricevendo STDIN (da cato un reindirizzamento della shell) o semplicemente dando il nome del file a perl come un altro argomento e lasciarlo aprire il file.

Nel mio caso prima avevo bisogno delle linee più lunghe, quindi ho scambiato $ae $bnel confronto.


Questa è la soluzione migliore perché awk causa un ordinamento imprevisto quando il file di input contiene righe numeriche e alfanumeriche Qui il comando online: $ cat testfile | perl -e 'print sort {lunghezza ($ a) <=> lunghezza ($ b)} <>'
alemol

Veloce! 465.000 file di riga (una parola per riga) in <1 secondo, quando l'output viene reindirizzato in un altro file - quindi:cat testfile.txt | perl -e 'print sort { length($a) <=> length($b) } <>' > out.txt
cssyphus

Windows con StrawberryPerl funziona:type testfile.txt | perl -e "print sort { length($a) <=> length($b) } <>" > out.txt
bryc,

14

Prova invece questo comando:

awk '{print length, $0}' your-file | sort -n | cut -d " " -f2-

10

Risultati benchmark

Di seguito sono riportati i risultati di un benchmark tra le soluzioni di altre risposte a questa domanda.

Metodo di prova

  • 10 corse sequenziali su una macchina veloce, in media
  • Perl 5.24
  • awk 3.1.5 (gawk 4.1.0 volte erano ~ 2% più veloci)
  • Il file di input è una mostruosità di 550 MB, 6 milioni di linee (British National Corpus txt)

risultati

  1. La perlsoluzione di Caleb impiegò 11,2 secondi
  2. la mia perlsoluzione ha richiesto 11,6 secondi
  3. La awksoluzione n. 1 di neillb ha richiesto 20 secondi
  4. La awksoluzione n. 2 di neillb ha richiesto 23 secondi
  5. la awksoluzione di anubhava ha impiegato 24 secondi
  6. La awksoluzione di Jonathan impiegò 25 secondi
  7. La bashsoluzione di Fretz impiega 400 volte più a lungo delle awksoluzioni (utilizzando un caso di prova troncato di 100000 linee). Funziona bene, richiede solo un'eternità.

perlOpzione extra

Inoltre, ho aggiunto un'altra soluzione Perl:

perl -ne 'push @a, $_; END{ print sort { length $a <=> length $b } @a }' file

6

Pure Bash:

declare -a sorted

while read line; do
  if [ -z "${sorted[${#line}]}" ] ; then          # does line length already exist?
    sorted[${#line}]="$line"                      # element for new length
  else
    sorted[${#line}]="${sorted[${#line}]}\n$line" # append to lines with equal length
  fi
done < data.csv

for key in ${!sorted[*]}; do                      # iterate over existing indices
  echo -e "${sorted[$key]}"                       # echo lines with equal length
done

3

La length()funzione include spazi. Vorrei apportare solo piccole modifiche alla tua pipeline (anche evitando UUOC ).

awk '{ printf "%d:%s\n", length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]*://'

Il sedcomando rimuove direttamente le cifre e i due punti aggiunti dal awkcomando. In alternativa, mantenendo la formattazione da awk:

awk '{ print length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]* //'

2

Ho scoperto che queste soluzioni non funzioneranno se il tuo file contiene righe che iniziano con un numero, poiché verranno ordinate numericamente insieme a tutte le righe contate. La soluzione è fornire sortil -gflag -n(ordinamento numerico generale) anziché (ordinamento numerico):

awk '{ print length, $0 }' lines.txt | sort -g | cut -d" " -f2-

2
Ciao Markus. Non osservo il contenuto della linea (numerico o no) - al contrario della lunghezza della linea - come avere alcun effetto sull'ordinamento tranne nel caso di linee con lunghezze corrispondenti. È questo che intendevi? In questi casi, non ho trovato il passaggio da metodi di ordinamento -na quelli suggeriti -gper produrre alcun miglioramento, quindi mi aspetto di no. Ho ora affrontato, nella mia risposta, come vietare il sotto-ordinamento di linee di uguale lunghezza (usando --stable). Che tu intendessi o no, grazie per averlo portato alla mia attenzione! Ho anche aggiunto un input considerato con cui testare.
Neillb,

4
No, lasciami spiegare scomponendolo. Solo la awkparte genererà un elenco di linee con prefisso lunghezza della linea e uno spazio. Il piping su sort -nfunzionerà come previsto. Ma se una di quelle linee ha già un numero all'inizio, quelle linee inizieranno con lunghezza + spazio + numero. sort -nignora quello spazio e lo tratterà come un numero concatenato da lunghezza + numero. L'uso della -gbandiera si fermerà invece al primo spazio, producendo un ordinamento corretto. Provalo tu stesso creando un file con alcune righe con prefisso numerico ed esegui il comando passo dopo passo.
Markus Amalthea Magnuson,

1
Ho anche scoperto che sort -nignora lo spazio e produce un ordinamento errato. sort -ggenera l'ordine corretto.
Robert Smith,

Non riesco a riprodurre il problema descritto con -nin sort (GNU coreutils) 8.21. La infodocumentazione descrive -gcome meno efficiente e potenzialmente meno precisa (converte i numeri in float), quindi probabilmente non usarlo se non è necessario.
phils

nb documentazione per -n: "Ordina numericamente. Il numero inizia ogni riga ed è costituito da spazi opzionali, un segno '-' opzionale e zero o più cifre eventualmente separate da migliaia di separatori, opzionalmente seguite da un carattere decimale e zero o più cifre . Un numero vuoto viene considerato come "0". La locale "LC_NUMERIC" specifica il carattere del punto decimale e il separatore delle migliaia. Per impostazione predefinita, uno spazio vuoto è uno spazio o una scheda, ma la locale "LC_CTYPE" può cambiarlo. "
phils


2

1) soluzione awk pura. Supponiamo che la lunghezza della linea non possa essere più> 1024 allora

nome file cat | awk 'BEGIN {min = 1024; s = "";} {l = lunghezza ($ 0); if (l <min) {min = l; s = $ 0;}} END {stampa s} '

2) una soluzione bash di linea supponendo che tutte le righe abbiano solo 1 parola, ma possono essere rielaborate per ogni caso in cui tutte le righe hanno lo stesso numero di parole:

LINEE = $ (nome file gatto); per k in $ LINES; fare printf "$ k"; echo $ k | wc -L; fatto | ordina -k2 | head -n 1 | cut -d "" -f1


1

Ecco un metodo compatibile multibyte per ordinare le linee per lunghezza. Richiede:

  1. wc -m è a tua disposizione (ce l'ha macOS).
  2. Le impostazioni internazionali correnti supportano caratteri multibyte, ad esempio impostando LC_ALL=UTF-8. Puoi impostarlo nel tuo .bash_profile o semplicemente anteponendolo prima del seguente comando.
  3. testfile ha una codifica dei caratteri corrispondente alla tua locale (ad es. UTF-8).

Ecco il comando completo:

cat testfile | awk '{l=$0; gsub(/\047/, "\047\"\047\"\047", l); cmd=sprintf("echo \047%s\047 | wc -m", l); cmd | getline c; close(cmd); sub(/ */, "", c); { print c, $0 }}' | sort -ns | cut -d" " -f2-

Spiegare parte per parte:

  • l=$0; gsub(/\047/, "\047\"\047\"\047", l);← crea una copia di ogni riga in una variabile awk le fa doppio escape ogni volta che 'la riga può essere tranquillamente ripetuta come un comando shell ( \047è una virgoletta singola in notazione ottale).
  • cmd=sprintf("echo \047%s\047 | wc -m", l);← questo è il comando che eseguiremo, che fa eco alla linea di escape wc -m.
  • cmd | getline c;← esegue il comando e copia il valore del conteggio dei caratteri restituito nella variabile awk c.
  • close(cmd); ← chiudere la pipe al comando shell per evitare di colpire un limite di sistema sul numero di file aperti in un processo.
  • sub(/ */, "", c);← taglia lo spazio bianco dal valore del conteggio dei caratteri restituito da wc.
  • { print c, $0 } ← stampa il valore del conteggio dei caratteri della linea, uno spazio e la linea originale.
  • | sort -ns← ordina le righe (in base ai valori di conteggio dei caratteri anteposti) numericamente ( -n) e mantenendo un ordinamento stabile ( -s).
  • | cut -d" " -f2- ← rimuove i valori di conteggio dei caratteri anteposti.

È lento (solo 160 righe al secondo su un Macbook Pro veloce) perché deve eseguire un comando secondario per ogni riga.

In alternativa, basta farlo solo con gawk(dalla versione 3.1.5, gawk è a conoscenza del multibyte), che sarebbe significativamente più veloce. È un sacco di problemi fare tutte le escape e le virgolette doppie per passare in sicurezza le linee attraverso un comando shell da awk, ma questo è l'unico metodo che ho trovato che non richiede l'installazione di software aggiuntivo (gawk non è disponibile per impostazione predefinita su Mac OS).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.