2015-08-21 42 views
7

Czy istnieje sposób na wyodrębnienie unikalnych znaków każdej linii?Jak mogę znaleźć unikalne znaki na linię wprowadzania?

wiem, że mogę znaleźć unikalne linie pliku przy użyciu

sort -u file 

chciałbym określić unikalnych znaków każdej linii (coś jak sort -u dla każdej linii).

Dla wyjaśnienia: podany to wejście:

111223234213 
111111111111 
123123123213 
121212122212 

chciałbym uzyskać ten wynik:

1234 
1 
123 
12 

Odpowiedz

5

Korzystanie sed

sed ':;s/\(.\)\(.*\)\1/\1\2/;t' file 

Zasadniczo co robi to uchwycić charakter i sprawdzić, czy pojawia się nigdzie indziej na linii. Przechwytuje także wszystkie postacie między nimi. Następnie zastępuje to wszystko, w tym drugie wystąpienie, przy pierwszym wystąpieniu, a następnie to, co było między nimi.

t jest testem i przeskakuje do etykiety :, jeśli poprzednie polecenie zakończyło się pomyślnie. Następnie powtarza się, dopóki nie powiedzie się komenda s///, co oznacza, że ​​pozostają tylko unikalne znaki.

; po prostu oddziela polecenia.

1234 
1 
123 
12 

Zachowuje również porządek.

+0

To jest krótsze! Czym są ':;' i 't' zrobić? – user1436187

+0

@ user1436187 Dodano wyjaśnienie – 123

+1

Przyjmuję to jako odpowiedź! – user1436187

3

To nie dostać rzeczy w oryginalnej kolejności, ale to awk jedno-liner wydaje się działać:

awk '{for(i=1;i<=length($0);i++){a[substr($0,i,1)]=1} for(i in a){printf("%s",i)} print "";delete a}' input.txt 

rozpadł dla łatwiejszego czytania, to może być samodzielny tak:

#!/usr/bin/awk -f 

{ 
    # Step through the line, assigning each character as a key. 
    # Repeated keys overwrite each other. 
    for(i=1;i<=length($0);i++) { 
    a[substr($0,i,1)]=1; 
    } 

    # Print items in the array. 
    for(i in a) { 
    printf("%s",i); 
    } 

    # Print a newline after we've gone through our items. 
    print ""; 

    # Get ready for the next line. 
    delete a; 
} 

Oczywiście, ta sama koncepcja może być realizowane bardzo łatwo w czystej bash także:

#!/usr/bin/env bash 

while read s; do 
    declare -A a 
    while [ -n "$s" ]; do 
    a[${s:0:1}]=1 
    s=${s:1} 
    done 
    printf "%s" "${!a[@]}" 
    echo "" 
    unset a 
done < input.txt 

Zauważ, że to zależy od bash 4, ze względu na tablicy asocjacyjnej. I ten jeden robi dostać rzeczy w oryginalnej kolejności, ponieważ bash ma lepszą pracę utrzymywania kluczy tablicy w kolejności niż awk.

Wydaje mi się, że masz rozwiązanie z użyciem sed od Jose, choć wiąże się ono z kilkoma dodatkowymi parametrami. :)

Ostatnie wspomniane narzędzie to grep. Jestem prawie pewien, że nie możesz tego zrobić w tradycyjnym grep, ale być może jakaś dzielna dusza może zbudować wariant perl-regexp (tj. grep -P) używając -o i lookarounds. Potrzebują więcej kawy niż teraz.

1

Ten awk powinno działać:

awk -F '' '{delete a; for(i=1; i<=NF; i++) a[$i]; for (j in a) printf "%s", j; print ""}' file 
1234 
1 
123 
12 

tutaj:

-F '' złamie char rekordu przez char dając nam jeden znak w $1, $2 itp

Uwaga: Dla non -gnu awk use:

awk 'BEGIN{FS=""} {delete a; for(i=1; i<=NF; i++) a[$i]; 
     for (j in a) printf "%s", j; print ""}' file 
3

Innym rozwiązaniem

while read line; do 
    grep -o . <<< $line | sort -u | paste -s -d '\0' -; 
done < file 

grep -o . konwertować „linia wiersz” na „linię kolumnie”
sort -u sortowania liter i usunięto repetead litery
paste -s -d '\0' - konwersji „linia kolumna” na „linię rzędu”
- jako argument pliku do wklejenia, aby poinformować go, że używa standardowego wejścia.

+0

Innowacyjne rozwiązanie! Dla odniesienia, pierwszy skrypt w twojej rurze może zostać skrócony do '' s/./&/g''. I "sort | uniq' zwykle można zastąpić przez 'sort -u'. Co się stanie, jeśli jednym z znaków, które mają zostać przycięte na linii, jest w rzeczywistości przestrzeń? – ghoti

+0

@Ghoti zrobione, poprawione ..... wielkie dzięki –

+0

Bardzo proszę. Aha i uważaj na '\ n' wewnątrz skryptu sed. To, co masz teraz działa z GNU sed, ale nie z sed, które jest w FreeBSD, OSX, Solaris, itp. Jeśli piszesz rzeczy, które nie są przenośne, najlepiej jest zauważyć ten fakt, lub dostarczyć alternatywy, jak anubhava z jego rozwiązanie GAWK. – ghoti

2

Jednym ze sposobów, za pomocą :

perl -F -lane 'print do { my %seen; grep { !$seen{$_}++ } @F }' file 

Wyniki:

1234 
1 
123 
12 
+0

Zawsze intryguje mnie perl, ale rzadko ma to dla mnie sens. Czy możesz wyjaśnić, jak to działa? – ghoti

1

To może pracować dla Ciebie (GNU sed):

sed 's/\B/\n/g;s/.*/echo "&"|sort -u/e;s/\n//g' file 

Podział każda linia na serię linii . Unikalne sortowanie tych linii. Połącz wynik z powrotem w jedną linię.

+0

Idealny! Czy mógłbyś wyjaśnić tę część 's /.*/ echo" & "'. – user1436187

+0

@ user1436187 patrz [tutaj] (http://www.gnu.org/software/sed/manual/sed.html#Extended-Commands) dla polecenia 'e'. Pozwala na ocenę przestrzeni wzorów w bieżącej powłoce i umieszczenie wyniku w przestrzeni wzorów. – potong

0

Unikalne i sortowane alternatywą dla innych, z wykorzystaniem narzędzi GNU sed:

sed 's/\(.\)/\1\n/g' file | sort | uniq 

która produkuje jeden znak w każdym wierszu; Jeśli chcesz te na jednej linii, po prostu zrobić:

sed 's/\(.\)/\1\n/g' file | sort | uniq | sed ':a;N;$!ba;s/\n//g;' 

Ma to tę zaletę, pokazując znaki posortowanych, zamiast kolejności występowania.