Come trovare la linea con meno caratteri


22

Sto scrivendo uno script di shell, usando qualsiasi comando UNIX generale. Devo recuperare la riga che contiene il minor numero di caratteri (spazi inclusi). Possono esserci fino a circa 20 righe.

So che posso usare head -$L | tail -1 | wc -mper trovare il conteggio dei caratteri della riga L. Il problema è che l'unico metodo a cui riesco a pensare, usando quello, sarebbe quello di scrivere manualmente un disordine di istruzioni if, confrontando i valori.

Dati di esempio:

seven/7
4for
8 eight?
five!

Sarebbe tornato 4forpoiché quella riga aveva il minor numero di caratteri.

Nel mio caso, se più righe hanno la lunghezza più breve, dovrebbe essere restituita una singola. Non importa quale sia selezionato, purché sia ​​della lunghezza minima. Ma non vedo il danno nel mostrare entrambi i modi per altri utenti con altre situazioni.


5
Cosa succede se sono presenti più righe con una lunghezza di 4? Dovrebbero essere stampati anche?
caos,

Nel mio caso, se più righe hanno la lunghezza più breve, dovrebbe essere restituita una singola. Non importa quale sia selezionato, purché sia ​​della lunghezza minima. Ma non vedo il danno nel mostrare entrambi i modi per altri utenti con altre situazioni.
Matthew D. Scholefield,

Risposte:


13

Un modo Perl. Nota che se ci sono molte linee della stessa lunghezza più breve, questo approccio ne stampa solo una:

perl -lne '$m//=$_; $m=$_ if length()<length($m); END{print $m if $.}' file 

Spiegazione

  • perl -lne: -nsignifica "leggere il file di input riga per riga", -lfa sì che le nuove righe finali vengano rimosse da ciascuna riga di input e che una nuova riga venga aggiunta a ciascuna printchiamata; ed -eè lo script che verrà applicato a ciascuna riga.
  • $m//=$_: impostato $msulla riga corrente ( $_) a meno che non $msia definito. L' //=operatore è disponibile da Perl 5.10.0.
  • $m=$_ if length()<length($m): se la lunghezza del valore corrente di $mè maggiore della lunghezza della riga corrente, salvare la riga corrente ( $_) come $m.
  • END{print $m if $.}: una volta elaborate tutte le righe, stampa il valore corrente di $m, la riga più breve. Le if $.assicura che questo avviene solo quando il numero di riga ( $.definito), evitando stampare una riga vuota per l'ingresso vuoto.

In alternativa, poiché il tuo file è abbastanza piccolo da adattarsi alla memoria, puoi fare:

perl -e '@K=sort{length($a) <=> length($b)}<>; print "$K[0]"' file 

Spiegazione

  • @K=sort{length($a) <=> length($b)}<>: <>ecco un array i cui elementi sono le linee del file. Il sortfascicolerà secondo la loro lunghezza e le linee filtrate vengono salvati come matrice @K.
  • print "$K[0]": stampa il primo elemento dell'array @K: la riga più corta.

Se si desidera stampare tutte le linee più brevi, è possibile utilizzare

perl -e '@K=sort{length($a) <=> length($b)}<>; 
         print grep {length($_)==length($K[0])}@K; ' file 

1
Aggiungi -Cper misurare la lunghezza in termini di numero di caratteri anziché numero di byte. In una locale UTF-8, $$ha meno byte di (2 vs 3), ma più caratteri (2 vs 1).
Stéphane Chazelas,

17

Con sqlite3:

sqlite3 <<EOT
CREATE TABLE file(line);
.import "data.txt" file
SELECT line FROM file ORDER BY length(line) LIMIT 1;
EOT

Quello è il mio preferito qui, mai pensato a SQL ...
caos

2
Questo è lo stato del codice golf intelligente
shadowtalker

2
Questo leggerà l'intero file in memoria e / o creerà una seconda copia su disco? Se è così, è intelligente ma inefficiente.
John Kugelman supporta Monica il

1
@JohnKugelman Questo probabilmente assorbirà tutte e 4 le righe in un database solo di memoria temporanea (questo è ciò che straceindica). Se devi lavorare con file di grandi dimensioni (e il tuo sistema non si scambia), puoi forzarlo semplicemente aggiungendo un nome di file simile sqlite3 $(mktemp)e tutti i dati verranno scritti su disco.
FloHimself

Ottengo i seguenti errori: "" "xaa: 8146:" carattere "" "senza caratteri" "e" "" xaa: 8825: previsti 1 colonne ma trovati 2 - extra ignorati "" ". Il file è composto da documenti json 1 per ogni riga .
Ahmedov

17

Ecco una variante di una awksoluzione per stampare la prima riga minima trovata:

awk '
  NR==1 || length<len {len=length; line=$0}
  END {print line}
'

che può essere semplicemente esteso di una condizione per stampare tutte le linee minime:

awk '
  length==len {line=line ORS $0}
  NR==1 || length<len {len=length; line=$0}
  END {print line}'
'

12

Python esce abbastanza conciso, e il codice fa quello che dice sulla latta:

python -c "import sys; print min(sys.stdin, key=len),"

L'ultima virgola è oscura, lo ammetto. Impedisce che l'istruzione print aggiunga un'interruzione di riga aggiuntiva. Inoltre, puoi scrivere questo in Python 3 supportando 0 righe come:

python3 -c "import sys; print(min(sys.stdin, key=len, default='').strip('\n'))"


cosa dice la scatola?
Mikeserv,

@mikeserve: dice "stampa il minimo di sys.stdin, usando len come chiave" ;-)
Steve Jessop,

1
ahh. quindi nulla delle dimensioni binarie, del creep di dipendenza o dei tempi di esecuzione?
Mikeserv,

2
@mikeserv: no, la piccola stampa non è sulla scatola. È su un volantino consultivo in un classificatore chiuso a chiave, in una cantina, dietro una porta contrassegnata "attenti al leopardo".
Steve Jessop,

Gotcha - così in mostra.
Mikeserv,

10

Adoro sempre le soluzioni con script di shell pura (no exec!).

#!/bin/bash
min=
is_empty_input="yes"

while IFS= read -r a; do
    if [ -z "$min" -a "$is_empty_input" = "yes" ] || [ "${#a}" -lt "${#min}" ]; then
        min="$a"
    fi
    is_empty_input="no"
done

if [ -n "$a" ]; then
    if [ "$is_empty_input" = "yes" ]; then
        min="$a"
        is_empty_input="no"
    else
        [ "${#a}" -lt "${#min}" ] && min="$a"
    fi
fi

[ "$is_empty_input" = "no" ] && printf '%s\n' "$min"

Nota :

Si è verificato un problema con i byte NUL nell'input. Quindi, printf "ab\0\0\ncd\n" | bash this_scriptstampa abinvece di cd.


Questo è davvero il più puro. Tuttavia, la goffaggine dei test bashmi convincerebbe a convogliare invece un risultato intermedio sort.
Orione,

2
Hai provato a mettere in panchina il tuo non esecutivo! soluzione rispetto ad altri che lo fanno? Ecco un confronto delle differenze di prestazioni tra exec! e niente exec! soluzioni per un problema simile. l'esecuzione di un processo separato è molto raramente vantaggiosa quando esegue il ragionamento - in forme come var=$(get data)perché limita il flusso di dati a un singolo contesto - ma quando si spostano i dati attraverso una pipeline - in un flusso - ogni exec applicato è generalmente utile - perché abilita specialisti applicazione di programmi modulari solo dove necessario.
Mikeserv,

1
@DigitalTrauma - una stringa di cifre contigua espansa non è più o meno esente dalle condizioni che rendono necessaria la quotazione della shell rispetto a qualsiasi altra stringa espansa. $IFSnon è discriminatorio per le cifre, anche se non ce ne sono in un $IFSvalore predefinito , sebbene molte shell accettino una configurazione di ambiente preimpostata per $IFS- e quindi non è un valore predefinito particolarmente affidabile.
Mikeserv,


1
Grazie a tutti per i commenti e le valutazioni (alcuni rappresentanti dovrebbero andare su @cuonglm per aver corretto la mia risposta). Generalmente non consiglio ad altri di esercitarsi quotidianamente con gli script di shell pura, ma questa abilità può essere trovata molto utile in alcune condizioni estreme in cui non /bin/shè disponibile nient'altro che un collegamento statico . Mi è successo più volte con gli host SunOS4 con /usrperdita o alcuni .sodanneggiati, e ora nella moderna era di Linux ancora occasionalmente incontro situazioni simili con sistemi embedded o initrd di sistemi che non funzionano. BusyBox è una delle grandi cose che abbiamo acquisito di recente.
Yaegashi il

9

Ecco una zshsoluzione pura (stampa tutte le linee con la lunghezza minima, da file):

IFS=$'\n'; print -l ${(M)$(<file):#${~${(o@)$(<file)//?/?}[1]}}

Esempio di input:

seven/7
4for
8 eight?
five!
four

L'output è:

4for
four

Penso che abbia bisogno di una breve spiegazione :-)


Innanzitutto, impostiamo il separatore di campo interno su newline:

IFS=$'\n';

Fin qui tutto bene, ora la parte difficile. printusa il -lflag per stampare il risultato separato da newline invece che da spazi.

Ora, iniziamo dall'interno:

$(<file)

Il file viene letto riga per riga e trattato come un array. Poi:

${(o@)...//?/?}

La obandiera dice che il risultato dovrebbe essere ordinato in ordine crescente, i @mezzi per trattare anche il risultato come matrice. La parte dietro ( //?/?) è una sostituzione che sostituisce tutti i caratteri con a ?. Adesso:

${~...[1]}

Prendiamo il primo elemento array [1], che è il più corto, nel tuo caso è ora ????.

${(M)$(<file):#...}

La corrispondenza viene eseguita su ciascun elemento dell'array separatamente e gli elementi dell'array senza pari vengono rimossi ( M). Ogni elemento che corrisponde ????(4 caratteri) rimane nell'array. Quindi gli elementi rimanenti sono quelli che hanno 4 caratteri (i più corti).

Modifica: se hai bisogno solo di una delle linee più corte, questa versione modificata stampa la prima:

IFS=$'\n'; print -l ${${(M)$(<file):#${~${(o@)$(<file)//?/?}[1]}}[1]}

8
tr -c \\n 1 <testfile |   #first transform every [^\n] char to a 1
grep -nF ''           |   #next get line numbers
paste -d: - testfile  |   #then paste it together with itself
sort  -t: -nk2,2          #then sort on second field

... e il vincitore è ... linea 2, sembrerebbe.

2:1111:4for
4:11111:five!
1:1111111:seven/7
3:11111111:8 eight?

Ma il problema è che ogni linea deve essere più che raddoppiata per funzionare, quindi LINE_MAX è effettivamente dimezzata. La causa è che sta usando - cosa, una base 1? - per rappresentare la lunghezza della linea. Un approccio simile - e forse più ordinato - potrebbe essere quello di comprimere tali informazioni in streaming. La prima idea che mi viene in mente è che dovrei unexpand:

tr -c \\n \  <testfile    |   #transform all [^\n] to <space>
unexpand -t10             |   #squeeze every series of 10 to one tab
grep -nF ''               |   #and get the line numbers
sed    's/:/!d;=;:/;h;:big    #sed compares sequential lines
$P;$!N; /\(:[^ ]*\)\( *\)\n.*\1.*\2/!D     #newest line is shorter or...
        g;/:./!q;b big'   |   #not; quit input entirely for blank line
sed -f - -e q testfile        #print only first occurrence of shortest line

Che stampa ...

2
4for

Un altro, solo sed:

sed -n '/^\n/D;s/\(.\)\(\n.*\)*/\1/g
$p;h;   s// /g;G;x;n;//!g;H;s// /g
G;      s/^\( *\)\(\n \1 *\)\{0,1\}\n//
D'      <infile >outfile

La sintassi è conforme agli standard, ma ciò non garantisce che i vecchi sedgestiranno \(reference-group\)\{counts\}correttamente quelli che non lo fanno.

In pratica applica lo stesso regexp all'input ripetutamente, il che può essere molto utile quando è il momento di compilarli. Quel modello è:

\(.\)\(\n.*\)*

Che corrisponde a stringhe diverse in modi diversi. Per esempio:

string1\nstring2\nstring3

... corrisponde a sin \1e ''alla stringa null in \2.

1\nstring2\nstring3

... è abbinato a 1in \1e \nstring2\nstring3in\2

\nstring2\nstring3

... corrisponde a \nin \1e ''alla stringa null in \2. Ciò sarebbe problematico se ci fosse la possibilità che si \nverifichi una ewline in testa allo spazio del modello, ma i comandi /^\n/De //!gsono usati per impedirlo. Ho usato, [^\n]ma altri bisogni di questo piccolo script hanno reso la portabilità una preoccupazione e non ero soddisfatto dei molti modi in cui è spesso interpretato male. Inoltre, .è più veloce.

\nstring2
string1

... match \ne sancora in \1ed entrambi ottengono la ''stringa null in \2. Le righe vuote non corrispondono affatto.

Quando il pattern viene applicato a livello gvaginale, i due bias - sia il bias più standard a sinistra sia il \nbias ewline inferiore destro - sono controbilanciati per effettuare uno skip. Alcuni esempi:

s/\(.\)\(\n.*\)*/\1:\2/g
s/\(.\)\(\n.*\)*/\2\1:/g
s/\(.\)\(\n.*\)*/\1: /g
s/\(.\)\(\n.*\)*/ :\2/g

... se tutti applicati (non in successione) alla seguente stringa ...

string1\nstring2

... lo trasformerà in ...

s:t:r:i:n:g:1:\nstring2
s:t:r:i:n:g:\nstring21:
s:t:r:i:n:g:1: 
 : : : : : : :\nstring2

Fondamentalmente uso regexp per gestire sempre solo la prima riga in qualsiasi spazio modello a cui lo applico. Ciò mi consente di destreggiarmi tra due diverse versioni di una linea di corrispondenza più breve mantenuta e la linea più recente senza ricorrere a cicli di test: ogni sostituzione applicata gestisce l'intero spazio del modello contemporaneamente.

Le diverse versioni sono necessarie per confronti letterali stringa / stringa - quindi deve esserci una versione di ogni riga in cui tutti i caratteri devono essere uguali. Ma ovviamente se l'uno o l'altro dovesse finire per essere effettivamente la prima riga più breve in ingresso, allora la linea stampata sull'output dovrebbe probabilmente essere la versione originale della linea - non quella che ho disinfettato / omogeneizzato per amor di confronto. E quindi ho bisogno di due versioni di ciascuno.

È un peccato che un'altra necessità sia molta commutazione del buffer per gestire lo stesso - ma almeno nessuno dei due buffer supera mai più delle quattro linee necessarie per rimanere aggiornato - e quindi forse non è terribile.

Ad ogni modo, per ogni ciclo la prima cosa che succede è una trasformazione sulla linea ricordata - perché l'unica copia effettivamente salvata è l'originale letterale - in ...

^               \nremembered line$

... e successivamente la nriga di input ext sovrascrive qualsiasi vecchio buffer. Se non contiene almeno un singolo carattere, viene effettivamente ignorato. Sarebbe molto più semplice usare solo alla qprima riga vuota, ma, beh, i miei dati di test avevano molti di questi e volevo gestire più paragrafi.

E quindi se contiene un personaggio, la sua versione letterale viene aggiunta alla linea ricordata e la sua versione di confronto spaziato viene posizionata all'inizio dello spazio del modello, in questo modo:

^   \n               \nremembered line\nnew$

L'ultima sostituzione viene applicata a quello spazio modello:

s/^\( *\)\(\n \1 *\)\{0,1\}\n//

Quindi, se la nuova riga può adattarsi allo spazio necessario per contenere la riga memorizzata con almeno un carattere da risparmiare, le prime due righe vengono sostituite, altrimenti solo la prima.

Indipendentemente dal risultato, la prima riga nello spazio modello viene sempre Deletta alla fine del ciclo prima di ricominciare. Ciò significa che se la nuova riga è più corta dell'ultima la stringa ...

new

... viene rimandato alla prima sostituzione del ciclo che rimuoverà sempre solo dal primo carattere newline attivo - e quindi rimane integro. Ma se non lo è, allora la stringa ...

remembered line\nnew

... inizierà invece il ciclo successivo e la prima sostituzione toglierà da esso la stringa ...

\nnew

...ogni volta.

Sull'ultima riga la riga memorizzata viene stampata sullo standard out, e quindi per i dati di esempio forniti, stampa:

4for

Ma, seriamente, usa tr.



Hai anche bisogno di inserire numeri di riga? La mia lettura del PO è che è richiesta solo la linea più breve, e non necessariamente il numero di linea di quella linea. Non credo che sia dannoso mostrarlo per completezza.
Digital Trauma,

@DigitalTrauma - nah, probabilmente no. Ma non è molto utile senza di loro - e arrivano così a buon mercato. Quando lavoro uno stream preferisco sempre includere un mezzo per riprodurre in modo identico l'input originale nell'output: i numeri di riga lo rendono possibile qui. Ad esempio, per trasformare i risultati del primo giro gasdotto: REINPUT | sort -t: -nk1,1 | cut -d: -f3-. E il secondo è una semplice questione di includere un altro sed --expressioncopione alla coda.
Mikeserv,

@DigitalTrauma - oh, e nel primo esempio i numeri di riga non influenzano sortil comportamento 's come tie-breaker quando le linee stessa lunghezza verificano in ingresso - così la linea che si verificano prima galleggia sempre all'inizio in quel caso.
Mikeserv,

7

Provare:

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

L'idea è quella di utilizzare awkper stampare prima la lunghezza di ogni riga. Questo apparirà come:

echo "This is a line of text" | awk '{print length, $0}'
22 This is a line of text

Quindi, usa il conteggio dei personaggi per ordinare le righe sort, cutper sbarazzarti del conteggio e headmantenere la prima riga (quella con il minor numero di caratteri). Ovviamente puoi usare tailper ottenere la linea con il maggior numero di personaggi in questo caso.

(Questo è stato adottato da questa risposta )


+1 per la logica ma non funzionerà in tutti i casi. Se le due righe hanno lo stesso numero di caratteri e quale è minimo. Ti darà solo la prima riga che si incontra a causa dihead -1
Thushi,

Per ottenere la riga più lunga, è un po 'più efficiente invertire l'ordinamento piuttosto che usarlo tail(poiché headpuò uscire non appena il lavoro è terminato, senza leggere il resto del suo input).
Toby Speight,

@Thushi Usando un po 'di regex, dopo aver stampato i numeri di riga, tutto tranne le righe con lo stesso numero della riga 1, potrebbe essere rimosso, producendo così tutte le linee più brevi.
Matthew D. Scholefield,

5

Con POSIX awk:

awk 'FNR==1{l=$0;next};length<length(l){l=$0};END{print l}' file

Non funzionerà se più di una riga ha lo stesso numero di caratteri e anche questo è minimo.
Thushi,

@Thushi: riporterà la prima riga minima.
cuonglm,

Sì, ma questo non è corretto, giusto? Anche le altre righe hanno il numero minimo di caratteri.
Thushi,

1
@Thushi: che non menziona il requisito OP, in attesa di aggiornamento dall'OP.
cuonglm,

3
Non credo sia Lstata la migliore lettera da scegliere per nominare la variabile: D Qualcosa del genere minrenderebbe le cose più chiare
fedorqui,

3

Prendendo in prestito alcune delle idee di @ mikeserv:

< testfile sed 'h;s/./:/g;s/.*/expr length "&"/e;G;s/\n/\t/' | \
sort -n | \
sed -n '1s/^[0-9]+*\t//p'

Il primo sedfa quanto segue:

  • h salva la riga originale nel buffer di mantenimento
  • Sostituisci ogni carattere in linea con :- questo serve a rimuovere qualsiasi pericolo di iniezione di codice
  • Sostituisci l'intera riga con expr length "whole line"- questa è un'espressione shell che può essere valutata
  • Il comando e tos è un'estensione GNU sed per valutare lo spazio del pattern e rimettere il risultato nello spazio del pattern.
  • G accoda una nuova riga e il contenuto dello spazio di trattenimento (la linea originale) allo spazio del motivo
  • la finale ssostituisce la nuova riga con una scheda

Il numero di caratteri ora è un numero all'inizio di ogni riga, quindi sort -nordina per lunghezza della riga.

Il finale sedrimuove quindi tutte le righe tranne la prima (più corta) e la lunghezza della linea e stampa il risultato.


1
@mikeserv Sì, penso che exprsia più bello qui. Sì, egenererà una shell per ogni riga. Ho modificato l'espressione sed in modo che sostituisca ogni carattere nella stringa con un :prima dell'eval che penso dovrebbe rimuovere ogni possibilità di iniezione di codice.
Digital Trauma,

Di solito opterei xargs exprpersonalmente - ma, oltre a evitare un guscio intermedio, è probabilmente più una cosa stilistica. Mi piace comunque.
Mikeserv,

3

Mi è venuto in mente che tutto è possibile in una sola sedespressione. Non è carino:

$ sed '1h;s/.*/&\n&/;G;:l;s/\n[^\n]\([^\n]*\)\n[^\n]/\n\1\n/;tl;/\n\n/{s/\n.*//;x};${x;p};d' testfile
4for
$ 

Abbattere questo:

1h            # save line 1 in the hold buffer (shortest line so far)
s/.*/&\n&/    # duplicate the line with a newline in between
G             # append newline+hold buffer to current line
:l            # loop start
s/\n[^\n]\([^\n]*\)\n[^\n]/\n\1\n/
              # attempt to remove 1 char both from current line and shortest line
tl            # jump back to l if the above substitution succeeded
/\n\n/{       # matches if current line is shorter
  s/\n.*//    # remove all but original line
  x           # save new shortest line in hold buffer
}
${            # at last line
  x           # get shortest line from hold buffer
  p           # print it
}
d             # don't print any other lines

La BSD sed in OS X è un po 'più schizzinosa con le nuove linee. Questa versione funziona per entrambe le versioni BSD e GNU di sed:

$ sed -e '1h;G;s/\([^\n]*\)\(\n\)\(.*\)/\1\2\1\2\3/;:l' -e 's/\(\n\)[^\n]\([^\n]*\n\)[^\n]/\1\2/;tl' -e '/\n\n/{s/\n.*//;x;};${x;p;};d' testfile
4for
$

Nota che si tratta più di una risposta "perché possibile" che di un serio tentativo di dare una risposta alle migliori pratiche. Immagino significhi che ho giocato troppo code-colf


@mikeserv Da man sedsu OS X: "La sequenza di escape \ n corrisponde a un carattere di nuova riga incorporato nello spazio modello" . Quindi penso che GNU sed permetta \nnel regex e nella sostituzione, mentre BSD consente solo \nnel regex e non nella sostituzione.
Digital Trauma,

Prendere in prestito \ndallo spazio pattern è una buona idea e funzionerebbe nella seconda s///espressione, ma l' s/.*/&\n&/espressione sta inserendo a \nnello spazio pattern in cui non ce n'era prima. Anche BSD sed sembra richiedere letteralmente nuove righe dopo le definizioni e i rami delle etichette.
Digital Trauma,

1
Quelle newline sono delimitatori di parametri - ne hai bisogno per delimitare qualsiasi comando che potrebbe accettare un parametro arbitrario - almeno, questo è ciò che dice la specifica. La specifica dice anche che uno sedscript deve essere un file di testo, tranne per il fatto che non deve finire con una nuova riga . Quindi di solito puoi delimitarli anche come argomenti separati - sed -e :\ label -e :\ label2e così via. Dal momento che lo stai facendo 1h, puoi semplicemente passare a una logica basata su x;Hper ottenere la tua nuova riga - e puoi tagliare una nuova riga principale dallo spazio del modello alla fine del ciclo senza tirare una nuova riga con D.
Mikeserv,

@mikeserv Nice. Sì, ho inserito la nuova riga di cui avevo bisogno facendo il Gprimo e cambiando l' s///espressione. Suddividendolo usando si -econsente a tutto di andare su una (lunga) linea senza letterali nuove linee.
Digital Trauma,

Anche la \nfuga è specificata per sedLHS, e penso che questa sia la dichiarazione della specificazione alla lettera, tranne per il fatto che anche le espressioni di parentesi POSIX sono specificate in modo tale che tutti i caratteri perdano il loro significato speciale - (includendo esplicitamente \\) - all'interno di uno tranne le parentesi quadre, il trattino come separatore di intervallo e punto, uguale, punto di inserimento, due punti per confronto, equivalenza, negazione e classi.
Mikeserv,

2

Un'altra soluzione perl: memorizzare le linee in un hash-of-array, la chiave hash è la lunghezza della linea. Quindi, stampare le linee con il tasto minimo.

perl -MList::Util=min -ne '
    push @{$lines{ length() }}, $_;
} END {
    print @{$lines{ min keys %lines }};
' sample 
4for

Puoi usare push @{$lines{+length}};e print @{$lines{+min keys %lines}};per scrivere meno :)
cuonglm

Se stavo giocando a golf, non avrei nemmeno usato il nome variabile "lines":perl -MList::Util=min -nE'push @{$l{+length}},$_}END{say@{$l{min keys%l}}' sample
glenn jackman

+1 per una versione non giocata a golf (che funziona!), Anche se solo per la stampa tutte le varianti. - perldiventa un po 'nodoso per quelli di noi che non sono all'altezza della perlnatura criptica. BTW. il golf saystampa una riga vuota spuria alla fine dell'output.
Peter

2

Per ottenere solo la prima linea più corta:

f=file; sed -n "/^$(sed 's/./1/g' $f | sort -ns | sed 's/././g;q')$/{p;q}" $f

Per ottenere tutti i lanugine più brevi, basta passare {p;q}ap


Un altro metodo (alquanto insolito) è quello di sortfare l' ordinamento effettivo per lunghezza . È relativamente lento anche con linee corte e diventa notevolmente più lento all'aumentare della lunghezza della linea.
Tuttavia, trovo piuttosto interessante l'idea di ordinare in base a tasti sovrapposti . Lo sto pubblicando nel caso in cui altri possano trovarlo interessante / informativo.

Come funziona:
ordina per lunghezza-varianti della stessa chiave - key 1che si estende su tutta la riga
Ogni variante di chiave successiva incrementa la lunghezza della chiave di un carattere, fino alla lunghezza della linea più lunga del file (determinata da wc -L)

Per ottenere solo la prima linea più corta (ordinata):

f=file; sort -t'\0' $(seq -f "-k1.%0.0f" $(<"$f" wc -L) -1 1) "$f" | head -n1

che è lo stesso di:

f=file.in; 
l=$(<"$f" wc -L)
k=$(seq -f "-k1.%0.0f" $l -1 1) 
sort -st'\0' $k "$f" | head -n1

2

Supponendo che le linee vuote non siano considerate la linea più breve e che possano esistere linee vuote, funzionerà il seguente AWK puro:

awk '
    {
        len   = length;
        a[$0] = len
    }
    !len { next }
    !min { min = len }
    len < min { min = len }
    END {
        for (i in a)
            if (min == a[i])
                print i
    }
' infile.txt

2

Che ne dici di usare sort?

awk '{ print length($0) "\t" $0 }' input.txt | sort -n | head -n 1 | cut -f2-

1

Con GNU awk

gawk '
    {
         a[length]=$0
    };
    END
    {
        PROCINFO["sorted_in"]="@ind_num_asc";
        for (i in a)
        {
            print a[i]; 
            exit
        }
    }
    ' file
  • Leggi ogni riga in un array indicizzato dalla lunghezza della riga.

  • Impostato PROCINFO["sorted_in"]per @ind_num_ascforzare la scansione dell'array in modo che venga ordinato dall'indice dell'array, ordinato numericamente

  • L'impostazione PROCINFOnel modo sopra impone che la linea con la lunghezza più piccola venga rilevata per prima nell'attraversamento dell'array. Quindi stampa il primo elemento dall'array ed esci

Questo ha lo svantaggio di essere un nlognpo 'alcuni degli altri approcci sono nin tempo


1

Metodo di strumenti di shell di livello medio, senza sedo awk:

f=inputfile
head -n $(xargs -d '\n' -L 1 -I % sh -c 'exec echo "%" | wc -c' < $f | 
          cat -n | sort -n -k 2 | head -1 | cut -f 1)  $f | tail -1

Sarebbe bello non aver bisogno di una $fvariabile; Ho un'idea che potrebbe essere possibile usando in teequalche modo ...
agc
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.