Jak znaleźć linię z najmniejszą liczbą znaków


22

Piszę skrypt powłoki, używając dowolnych ogólnych poleceń UNIX. Muszę pobrać wiersz, który ma najmniej znaków (łącznie z białymi odstępami). Może być do około 20 linii.

Wiem, że mogę użyć head -$L | tail -1 | wc -mliczby znaków w wierszu L. Problem polega na tym, że jedyną metodą, o której mogę pomyśleć, byłoby ręczne napisanie bałaganu instrukcji if, porównując wartości.

Przykładowe dane:

seven/7
4for
8 eight?
five!

Wróci, 4forponieważ linia ta ma najmniej znaków.

W moim przypadku, jeśli wiele linii ma najkrótszą długość, jedna powinna zostać zwrócona. Nie ma znaczenia, który z nich zostanie wybrany, o ile ma on minimalną długość. Ale nie widzę szkody w pokazywaniu obu sposobów innym użytkownikom w innych sytuacjach.


5
Co się stanie, jeśli istnieje wiele linii o długości 4? Czy też powinny być wydrukowane?
chaos

W moim przypadku, jeśli wiele linii ma najkrótszą długość, jedna powinna zostać zwrócona. Nie ma znaczenia, który z nich zostanie wybrany, o ile ma on minimalną długość. Ale nie widzę szkody w pokazywaniu obu sposobów innym użytkownikom w innych sytuacjach.
Matthew D. Scholefield,

Odpowiedzi:


13

Perlowy sposób. Zauważ, że jeśli jest wiele linii o tej samej, najkrótszej długości, to podejście wydrukuje tylko jeden z nich:

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

Wyjaśnienie

  • perl -lne: -noznacza „odczytaj plik wejściowy wiersz po wierszu”, -lpowoduje usunięcie końcowych znaków nowej linii z każdej linii wejściowej i dodanie nowego wiersza do każdego printwywołania; i -ejest skryptem, który zostanie zastosowany do każdej linii.
  • $m//=$_: ustaw $mna bieżącą linię ( $_), chyba że $mzdefiniowano. //=Operator jest dostępny od Perl 5.10.0.
  • $m=$_ if length()<length($m): jeśli długość bieżącej wartości $mjest większa niż długość bieżącej linii, zapisz bieżącą linię ( $_) jako $m.
  • END{print $m if $.}: po przetworzeniu wszystkich linii wydrukuj bieżącą wartość $mnajkrótszej linii. W if $.gwarantuje, że dzieje się tak dopiero, gdy liczba linii ( $.) jest zdefiniowana, unikając drukowania pustą linię do pustego wkładu.

Alternatywnie, ponieważ plik jest wystarczająco mały, aby zmieścić się w pamięci, możesz:

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

Wyjaśnienie

  • @K=sort{length($a) <=> length($b)}<>: <>tutaj jest tablica, której elementami są linie pliku. sortJe sortować według ich długości i posortowane wiersze są zapisywane w tablicy @K.
  • print "$K[0]": wydrukuj pierwszy element tablicy @K: najkrótszą linię.

Jeśli chcesz wydrukować wszystkie najkrótsze linie, możesz użyć

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

1
Dodaj, -Caby zmierzyć długość pod względem liczby znaków zamiast liczby bajtów. W ustawieniach regionalnych UTF-8 $$ma mniej bajtów niż (2 vs 3), ale więcej znaków (2 vs 1).
Stéphane Chazelas,

17

Z sqlite3:

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

Ten jest tutaj moim ulubionym, nigdy nie myślałem o SQL ...
chaos

2
To jest sprytny kod golfowy
shadowtalker

2
Czy to wczyta cały plik do pamięci i / lub utworzy drugą kopię na dysku? Jeśli tak, jest sprytny, ale nieefektywny.
John Kugelman wspiera Monicę

1
@JohnKugelman To prawdopodobnie pochłonie całe 4 wiersze do tymczasowej bazy danych tylko w pamięci (to stracewskazuje). Jeśli potrzebujesz pracować z naprawdę dużymi plikami (a twój system nie zamienia się), możesz wymusić to, dodając nazwę pliku podobną do, sqlite3 $(mktemp)a wszystkie dane zostaną zapisane na dysk.
FloHimself

Pojawiają się następujące błędy: „” „xaa: 8146: nieokreślony„ znak ”„ ”” i „” „xaa: 8825: oczekiwano 1 kolumn, ale znaleziono 2 - dodatki zignorowano„ ”.” Plik zawiera dokumenty jsona 1 w każdym wierszu .
Ahmedov

17

Oto wariant awkrozwiązania drukowania pierwszej znalezionej linii minimalnej:

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

który można po prostu rozszerzyć o jeden warunek, aby wydrukować wszystkie minimalne wiersze:

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

12

Python wychodzi dość zwięźle, a kod robi to, co mówi na puszce:

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

Przyznaję, że ostatni przecinek jest niejasny. Zapobiega dodaniu instrukcji print do dodatkowego podziału wiersza. Dodatkowo możesz napisać to w Pythonie 3, obsługując 0 linii, takich jak:

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


co mówi puszka?
mikeserv

@mikeserve: mówi: „drukuje minimum sys.stdin, używając len jako klucza” ;-)
Steve Jessop

1
ach nic więc o wielkości binarnej, czasie pełzania zależności lub czasie wykonania?
mikeserv

2
@mikeserv: nie, mały druk nie jest na puszce. Znajduje się na ulotce informacyjnej w zamkniętej szafce na dokumenty, w piwnicy, za drzwiami z napisem „strzeż się lamparta”.
Steve Jessop

Gotcha - tak na wyświetlaczu.
mikeserv

10

Zawsze uwielbiam rozwiązania z czystym skryptem powłoki (bez 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"

Uwaga :

Wystąpił problem z bajtami NUL na wejściu. Więc printf "ab\0\0\ncd\n" | bash this_scriptdrukuje abzamiast cd.


To naprawdę jest najczystsze. Chociaż niezręczność testów bashprzekonałaby mnie do wprowadzenia pośredniego wyniku sort.
orion

2
Czy próbowałeś przetestować swojego bez egzekucji ! rozwiązanie kontra inne, które to robią? Oto porównanie różnic w wydajności między exec! i bez exec! rozwiązania podobnego problemu. wykonywanie osobnego procesu jest bardzo rzadko korzystne, gdy wykonuje pająki - w postaciach takich jak var=$(get data)ponieważ ogranicza przepływ danych do jednego kontekstu - ale gdy przenosisz dane przez potok - w strumieniu - każdy zastosowany exec jest ogólnie pomocny - ponieważ umożliwia wyspecjalizowanie stosowanie programów modułowych tylko w razie potrzeby.
mikeserv

1
@DigitalTrauma - rozwinięty ciąg ciągły cyfr nie jest mniej lub bardziej zwolniony z warunków, które powodują konieczność cytowania powłoki niż jakikolwiek inny ciąg rozwinięty. $IFSnie jest dyskryminujący cyfrowo - nawet jeśli nie ma żadnej $IFSwartości domyślnej , chociaż wiele powłok zaakceptuje wstępnie skonfigurowaną konfigurację środowiska $IFS- i dlatego nie jest to szczególnie niezawodna wartość domyślna.
mikeserv


1
Dziękuję wszystkim za komentarze i opinie (część przedstawicieli powinna udać się na @cuonglm, aby poprawić moją odpowiedź). Zasadniczo nie polecam innym codziennego ćwiczenia skryptów w czystej powłoce, ale ta umiejętność może być bardzo przydatna w niektórych ekstremalnych warunkach, w których nie /bin/shjest dostępne nic innego jak statyczne powiązanie . Zdarzyło mi się to kilka razy z hostami SunOS4 z /usrzagubionymi lub .souszkodzonymi, a teraz w epoce Linuksa wciąż czasami spotykam podobne sytuacje z systemami osadzonymi lub systemami z błędem rozruchu. BusyBox to jedna z wielkich rzeczy, które niedawno nabyliśmy.
yaegashi,

9

Oto czyste zshrozwiązanie (drukuje wszystkie linie o minimalnej długości, od file):

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

Przykładowe dane wejściowe:

seven/7
4for
8 eight?
five!
four

Dane wyjściowe to:

4for
four

Myślę, że potrzebuje krótkiego wyjaśnienia :-)


Najpierw ustawiamy wewnętrzny separator pól na nowy wiersz:

IFS=$'\n';

Jak dotąd tak dobrze, teraz najtrudniejsza część. printużywa -lflagi do wydrukowania wyniku oddzielonego znakiem nowej linii zamiast spacji.

Teraz zaczynamy od środka:

$(<file)

Plik jest odczytywany wiersz po wierszu i traktowany jak tablica. Następnie:

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

oFlaga mówi, że wynik powinien być uporządkowane w kolejności rosnącej, te @środki do leczenia wynik jako tablicę też. Część za ( //?/?) jest podstawieniem i zastępuje wszystkie znaki znakiem ?. Teraz:

${~...[1]}

Bierzemy pierwszy element tablicy [1], który jest najkrótszy, w twoim przypadku jest teraz ????.

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

Dopasowywanie jest wykonywane dla każdego elementu tablicy osobno, a niedopasowane elementy tablicy są usuwane ( M). Każdy pasujący element ????(4 znaki) pozostaje w tablicy. Pozostałe elementy to te, które mają 4 znaki (najkrótsze).

Edycja: Jeśli potrzebujesz tylko jednej z najkrótszych linii, ta zmodyfikowana wersja drukuje pierwszą:

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

... a zwycięzcą jest ... linia 2, wydaje się.

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

Problem polega jednak na tym, że każda linia musi mieć ponad dwukrotną długość, aby mogła działać - więc LINE_MAX jest skutecznie zmniejszony o połowę. Powodem jest to, że używa - co, podstawa 1? - do reprezentowania długości linii. Podobnym - i być może bardziej uporządkowanym - podejściem może być kompresowanie tych informacji w strumieniu. Pierwszym pomysłem, który przychodzi mi do głowy, jest to, że powinienem to unexpandzrobić:

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

To drukuje ...

2
4for

Kolejny, po prostu 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

Składnia jest zgodna ze standardami - ale to nie gwarantuje, że każdy stary sedporadzi sobie \(reference-group\)\{counts\}poprawnie - wielu tego nie robi.

Zasadniczo stosuje to samo wyrażenie regularne do danych wejściowych wielokrotnie - co może być bardzo korzystne, gdy nadszedł czas na ich kompilację. Ten wzór to:

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

Który pasuje do różnych ciągów znaków na różne sposoby. Na przykład:

string1\nstring2\nstring3

... jest dopasowywane z sin \1i ''łańcuchem zerowym w \2.

1\nstring2\nstring3

... jest dopasowane 1w \1i \nstring2\nstring3w\2

\nstring2\nstring3

... jest dopasowywane z \nin \1i ''łańcuchem zerowym w \2. Byłoby to problematyczne, gdyby istniała jakakolwiek szansa, że \newline pojawi się na początku przestrzeni wzorów - ale polecenia /^\n/Di //!gsłużą do tego. Korzystałem z niego, [^\n]ale inne potrzeby związane z tym małym skryptem sprawiły, że przenośność była problemem i nie byłem zadowolony z wielu sposobów, w jaki często jest on źle interpretowany. Plus .jest szybszy.

\nstring2
string1

... dopasowuj \ni sponownie do \1i oba uzyskują ''ciąg zerowy \2. Puste linie w ogóle się nie zgadzają.

Kiedy wzór jest nakładany globalnie, dwa odchylenia - zarówno odchylenie standardowe najbardziej lewe, jak i mniejsze \nodchylenie ewline po prawej stronie - są równoważone w celu pominięcia. Kilka przykładów:

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

... jeśli wszystkie zastosowano (nie po kolei) do następującego ciągu ...

string1\nstring2

... przekształci go w ...

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

Zasadniczo używam wyrażenia regularnego, aby zawsze obsługiwać tylko pierwszą linię w dowolnej przestrzeni wzorców, do której ją stosuję. To pozwala mi żonglować dwiema różnymi wersjami zarówno zachowanej linii o najkrótszym jak dotąd dopasowaniu, jak i najnowszej linii bez uciekania się do pętli testowych - każda zastosowana zamiana obsługuje całą przestrzeń wzorców naraz.

Różne wersje są niezbędne do dosłownego porównywania ciągów / ciągów - dlatego musi istnieć wersja każdego wiersza, w której wszystkie znaki są równe. Ale oczywiście, jeśli jedno lub drugie powinno faktycznie zakończyć się najwcześniejszą pojawiającą się najkrótszą linią na wejściu, to linia drukowana na wyjściu powinna być prawdopodobnie oryginalną wersją linii - a nie tą, którą zdezynfekowałem / zhomogenizowałem dla porównania. Potrzebuję więc dwóch wersji każdego z nich.

To niefortunne, że kolejną koniecznością jest wiele przełączeń buforów, aby poradzić sobie z tym samym - ale przynajmniej żaden bufor nigdy nie przekracza więcej niż czterech linii potrzebnych do utrzymania aktualności - a więc może nie jest straszny.

W każdym razie dla każdego cyklu pierwszą rzeczą, która się dzieje, jest transformacja zapamiętanej linii - ponieważ jedyną faktycznie zapisaną kopią jest dosłowny oryginał - w ...

^               \nremembered line$

... a następnie nlinia wejściowa ext zastępuje stary bufor. Jeśli nie zawiera co najmniej jednego znaku, jest skutecznie ignorowany. O wiele łatwiej byłoby po prostu skorzystać qz pierwszego pustego wiersza, ale cóż, moje dane testowe zawierały wiele takich i chciałem obsłużyć wiele akapitów.

I tak, jeśli zawiera znak, jego dosłowna wersja jest dołączana do zapamiętanej linii, a jego wersja porównawcza z odstępami jest umieszczana na początku przestrzeni wzorów, jak poniżej:

^   \n               \nremembered line\nnew$

Na koniec stosuje się podstawienie do tej przestrzeni wzorów:

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

Więc jeśli nowa linia zmieści się w przestrzeni potrzebnej do przechowywania zapamiętanej linii z co najmniej jednym znakiem do zaoszczędzenia, wówczas pierwsze dwie linie zostaną podstawione, w przeciwnym razie tylko pierwsza.

Niezależnie od wyniku, pierwsza linia w przestrzeni wzorów jest zawsze Dusuwana na końcu cyklu przed ponownym uruchomieniem. Oznacza to, że jeśli nowy wiersz jest krótszy niż ostatni ciąg ...

new

... jest odsyłany z powrotem do pierwszej substytucji w cyklu, która zawsze usuwa tylko pierwszy znak nowego wiersza - i dlatego pozostaje cała. Ale jeśli nie jest to ciąg ...

remembered line\nnew

... zamiast tego rozpocznie się następny cykl, a pierwsze podstawienie usunie z niego ciąg ...

\nnew

...każdego razu.

W ostatnim wierszu zapamiętana linia jest wypisywana na standardowe wyjście, więc dla podanych danych przykładowych wypisuje:

4for

Ale poważnie, użyj tr.



Czy w ogóle potrzebujesz wstawić numery linii? Moim zdaniem OP jest taki, że wymagany jest tylko najkrótszy wiersz, a niekoniecznie numer tego wiersza. Chyba nie zaszkodzi wykazać to dla kompletności.
Cyfrowa trauma

@DigitalTrauma - prawdopodobnie nie. Ale bez nich nie jest bardzo przydatna - i przychodzą tak tanio. Podczas pracy ze strumieniem zawsze wolę zawierać sposób identycznego odtworzenia oryginalnego wejścia na wyjściu - pozwalają na to numery linii. Na przykład, aby włączyć wyniki pierwszego wokół rurociągu: REINPUT | sort -t: -nk1,1 | cut -d: -f3-. A druga to prosta kwestia włączenia kolejnego sed --expressionskryptu do ogona.
mikeserv

@DigitalTrauma - oh, i w pierwszym przykładzie numery linii nie wpływają sort„s zachowanie jako tie-breaker gdy występują linie tej samej długości na wejściu - tak najwcześniej występujących linia zawsze unosi się do góry w tej sprawie.
mikeserv

7

Próbować:

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

Chodzi o to, aby awknajpierw wydrukować długość każdej linii. Będzie to wyglądać jak:

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

Następnie użyj liczby znaków, aby posortować linie sort, cutpozbyć się liczby i headzachować pierwszą linię (tę z najmniejszą liczbą znaków). W tym przypadku możesz oczywiście użyć tailwiersza zawierającego najwięcej znaków.

(Zostało przyjęte na podstawie tej odpowiedzi )


+1 za logikę, ale nie będzie działać we wszystkich przypadkach. Jeśli dwa wiersze mają taką samą liczbę znaków i która jest minimalna. To da ci tylko pierwszą linię, która napotkasz z powoduhead -1
Thushi

Aby uzyskać najdłuższą linię, nieco bardziej wydajne jest odwrócenie sortowania niż użycie tail(ponieważ headmoże wyjść, gdy tylko zadanie zostanie wykonane, bez odczytywania reszty danych wejściowych).
Toby Speight,

@Thushi Używając odrobiny wyrażenia regularnego, po wydrukowaniu numerów linii, wszystko oprócz linii o tym samym numerze co linia 1, można usunąć, uzyskując w ten sposób wszystkie najkrótsze linie.
Matthew D. Scholefield,

5

Z POSIX awk:

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

Nie zadziała, jeśli więcej niż jedna linia ma taką samą liczbę znaków i jest również minimalna.
Thushi,

@Thushi: Zgłasza pierwszą minimalną linię.
cuonglm

Tak, ale to nie jest poprawne wyjście, prawda? Nawet inne wiersze mają minimalną liczbę znaków.
Thushi,

1
@Thushi: To nie wspomina o wymaganiu OP, oczekiwanie na aktualizację OP.
cuonglm

3
Nie wydaje mi się, żeby to Lbyła najlepsza litera do wybrania nazwy zmiennej: D Coś takiego minwyjaśniłoby
sprawę

3

Pożyczanie niektórych pomysłów @ mikeserv:

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

Pierwszy sedwykonuje następujące czynności:

  • h zapisuje oryginalną linię w buforze wstrzymania
  • Zamień każdy znak w wierszu na :- ma to na celu usunięcie niebezpieczeństwa wstrzyknięcia kodu
  • Zamień całą linię na expr length "whole line"- jest to wyrażenie powłoki, które można ocenić
  • Polecenie es to GNU sed rozszerzenie do oceny przestrzeni wzorców i ponownego umieszczenia wyniku w przestrzeni wzorców.
  • G dołącza nową linię i zawartość przestrzeni wstrzymania (pierwotna linia) do przestrzeni wzoru
  • ostatni szastępuje nowy wiersz tabulatorem

Liczba znaków jest teraz liczbą na początku każdej linii, więc sort -nsortuje się według długości linii.

Końcowy sednastępnie usuwa wszystkie oprócz pierwszej (najkrótszej) linii i długości linii i drukuje wynik.


1
@mikeserv Tak, myślę, że exprjest tu ładniej. Tak, espawnuje powłokę dla każdej linii. Zredagowałem wyrażenie sed tak, aby zastępowało każdy znak w ciągu ciągiem :przed eval, co moim zdaniem powinno usunąć wszelkie możliwości wstrzyknięcia kodu.
Cyfrowa trauma

Zwykle wolałbym xargs exprosobiście - ale oprócz unikania pośredniej powłoki, to chyba bardziej stylistyczna rzecz. W każdym razie lubię to.
mikeserv

3

Przyszło mi do głowy, że całość jest możliwa w jednym sedwyrażeniu. To nie jest ładne:

$ 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
$ 

Podział tego:

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

BSD sed w OS X jest nieco bardziej wybredny w przypadku nowych linii. Ta wersja działa zarówno dla wersji sed jak BSD i GNU:

$ 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
$

Zauważ, że jest to bardziej odpowiedź „ponieważ jest to możliwe” niż poważna próba udzielenia odpowiedzi na najlepszą praktykę. Wydaje mi się, że to oznacza, że ​​gram za dużo kodu-colf


@mikeserv Od man sedw systemie OS X: „Sekwencja zmiany znaczenia \ n odpowiada znakowi nowej linii osadzonemu w obszarze wzorów” . Więc myślę, że GNU sed zezwala \nna regex i na zamianę, podczas gdy BSD pozwala tylko \nna regex, a nie na zamianę.
Cyfrowa trauma

Pożyczanie \nz obszaru wzorców jest dobrym pomysłem i działałoby w drugim s///wyrażeniu, ale s/.*/&\n&/wyrażenie to wstawia znak \ndo obszaru wzorców, w którym wcześniej go nie było. Wydaje się również, że BSD sed wymaga dosłownie nowych linii po definicjach etykiet i rozgałęzieniach.
Cyfrowa trauma

1
Te nowe wiersze są ogranicznikami parametrów - potrzebujesz ich do ograniczenia dowolnej komendy, która może zaakceptować dowolny parametr - przynajmniej tak mówi specyfikacja. Specyfikacja mówi również, że sedskrypt powinien być plikiem tekstowym, ale nie musi kończyć się nowym wierszem . Możesz więc zwykle rozgraniczać je również jako osobne argumenty - sed -e :\ label -e :\ label2i tak dalej. Ponieważ i tak robisz 1h, możesz po prostu przełączyć się na logikę opartą na x;Huzyskiwaniu nowej linii - i możesz przyciąć wiodącą nową linię z przestrzeni wzorów na końcu cyklu bez wciągania nowej linii w / D.
mikeserv

@mikeserv Nice. Tak, wstawiłem nowy wiersz, którego potrzebowałem, wykonując Gpierwszy i zmieniając s///wyrażenie. Dzielenie go za pomocą -epozwala, aby wszystko przebiegało w jednym (długim) wierszu bez dosłownych nowych linii.
Cyfrowa trauma

\nUcieczka jest spec''d dla sed„s LHS, zbyt, i myślę, że to stwierdzenie jest spec verbatim, oprócz tego, że POSIX wyrażenia wysięgniki są również spec''d w taki sposób że wszystkie postacie tracą - specjalne znaczenie (jawnie łącznie \\) - w jednym z wyjątkiem nawiasów, myślnik jako separator zakresu, i kropka, równa się, karetka, dwukropek dla zestawienia, równoważności, negacji i klas.
mikeserv

2

Inne rozwiązanie perla: przechowuj linie w haszowaniu tablic, przy czym kluczem skrótu jest długość linii. Następnie wydrukuj linie z minimalnym kluczem.

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

Możesz używać push @{$lines{+length}};i print @{$lines{+min keys %lines}};mniej pisać :)
cuonglm

Gdybym grał w golfa, nie perl -MList::Util=min -nE'push @{$l{+length}},$_}END{say@{$l{min keys%l}}' sample
użyłbym

+1 dla wersji bez gry w golfa (co działa!), Ale tylko dla wersji drukowanej dla wszystkich wariantów. - perlrobi się trochę poważnie dla tych z nas, którzy nie są na równi perlz tajemniczą naturą. BTW. golfed saydrukuje fałszywą pustą linię na końcu.
Peter.O

2

Aby uzyskać tylko pierwszą najkrótszą linię:

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

Aby uzyskać wszystkie najkrótsze kłaczki, po prostu zmień {p;q}nap


Inna metoda (nieco nietypowy) ma mieć sortzrobić rzeczywisty sortowanie wg długości . Jest stosunkowo wolny nawet przy krótkich liniach i staje się znacznie wolniejszy wraz ze wzrostem długości linii.
Jednak pomysł sortowania według nakładających się klawiszy jest dość interesujący. Zamieszczam go na wypadek, gdyby inni również uznali to za interesujące / informacyjne.

Jak to działa:
Sortuj według wariantów długości tego samego klucza - key 1który obejmuje całą linię
Każdy kolejny wariant klucza zwiększa długość klucza o jeden znak, aż do długości najdłuższej linii pliku (określonej przez wc -L)

Aby uzyskać tylko pierwszą (posortowaną) najkrótszą linię:

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

który jest taki sam jak:

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

2

Zakładając, że puste linie nie są uważane za najkrótsze i że mogą istnieć puste linie, zadziała następujący czysty AWK:

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

Co z użyciem sortowania?

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

1

Z GNU awk

gawk '
    {
         a[length]=$0
    };
    END
    {
        PROCINFO["sorted_in"]="@ind_num_asc";
        for (i in a)
        {
            print a[i]; 
            exit
        }
    }
    ' file
  • Wczytaj każdą linię do tablicy indeksowanej według długości linii.

  • Ustaw PROCINFO["sorted_in"]się @ind_num_ascdo siły skanowania tablica zamawiane przez indeks tablicy, sortowane numerycznie

  • Ustawienie PROCINFOw powyższy sposób wymusza, aby linia o najmniejszej długości była pobierana jako pierwsza podczas przechodzenia przez tablicę. Więc wydrukuj pierwszy element z tablicy i wyjdź

Wadą jest to nlogn, że niektóre inne podejścia są nna czas


1

Mid-level metoda narzędzia powłoki, bez sedlub 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

Byłoby miło nie potrzebować $fzmiennej; Mam pojęcie, które może być możliwe w teejakiś sposób ...
agc
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.