Manipulacja ciągami znaków w konsoli linuksa

3 lipca, 2012 (07:26) | linux | By: konrad

Tym razem wpis z gatunku tips-and-tricks.

Dostałem w pliku listę pakietów, które miałem zainstalować w nowym systemie. Niestety, plik wyglądał mniej-więcej tak:

[kbechler@flame tmp]$ cat ./packages.txt
binutils-2.17.50.0.6
compat-libstdc++-33-3.2.3
compat-libstdc++-33-3.2.3 (32 bit)
elfutils-libelf-0.125
elfutils-libelf-devel-0.125
gcc-4.1.2
gcc-c++-4.1.2
glibc-2.5-24
glibc-2.5-24 (32 bit)
glibc-common-2.5
glibc-devel-2.5
glibc-devel-2.5 (32 bit)
glibc-headers-2.5
ksh-20060214
libaio-0.3.106
libaio-0.3.106 (32 bit)
libaio-devel-0.3.106
libaio-devel-0.3.106 (32 bit)
libgcc-4.1.2
libgcc-4.1.2 (32 bit)
libstdc++-4.1.2
libstdc++-4.1.2 (32 bit)
libstdc++-devel 4.1.2
make-3.81
sysstat-7.0.2
 
[kbechler@flame tmp]$

Wrzucenie tego bezpośrednio do YUMa teoretycznie powinno zadziałać, ale jest mało eleganckie, bo:

  • Ciąg znaków „(32bit)” nie zostanie rozpoznany jako poprawna nazwa pakietu, więc YUM będzie marudził, że nie znalazł wszystkiego, o co został poproszony,

  • Pakiety od chwili publikacji listy, którą dostałem, mogły zostać zaktualizowane i – żeby mieć pewność, że zainstalowałem ich najnowszej wersje – powinienem po instalacji jeszcze raz puścić YUMa z opcją „update”.

Pierwsze zadanie (które jest bohaterem niniejszego wpisu) to: jak przerobić listę pakietów tak, żeby usunąć z niej zbędne elementy w postaci informacji o ilości bitów oraz aktualnej wersji pakietu. Innymi słowy – jak pozbyć się znaków, które występują po ostatnim myślniku :-)

Najpierw przyszło mi do głowy nieśmiertelne i bardzo często przeze mnie używane polecenie cut. Niestety, po przejrzeniu manuala okazało się, że nie umie zrobić tego, co bym chciał, bo potrafi liczyć tylko od początku (ja potrzebuję liczyć od końca, bo ilość „elementów” w wierszach jest różna).

Przyszła kolej na AWKa. Nie udało mi się wprawdzie znaleźć problemu, którego by się nie dało AWKiem rozwiązać, ale wyszło mi coś, co nie wygląda zbyt zrozumiale:

awk -F'-' '{for(i=1;i<NF;i++){printf("%s",$i);if(i==NF-1)printf("\n");else printf("-");}}' ./packages.txt

I w tym momencie przypomniało mi się, że w Linuksie (hmmm, w innych systemach Uniksowych pewnie też) jest magiczna komenda rev, która odwraca ciąg znaków. Z połączenia tego (mało znanego) „narzędzia” oraz (zdecydowanie lepiej znanego) cut’a udało mi się zrobić coś takiego:

cat ./packages.txt | rev | cut -d- -f2- | rev

Żeby było już tak zupełnie poprawnie, to warto usunąć duplikaty:

cat ./packages.txt | rev | cut -d- -f2- | rev | sort | uniq

Pozostaje ostatnie pytanie: Jak to „wrzucić” do YUMa?
Ano nic prostszego:

sudo yum install `cat ./packages.txt | rev | cut -d- -f2- | rev | sort | uniq`

Metoda jest na pewno dość powolna i na pewno za pomocą AWKa, PERLa czy czegoś podobnego, dałoby się zrobić to bardziej wydajnie, jednak do jednorazowego przerobienia kilkuset linii jest zdecydowanie najprostsza do napisania i najbardziej czytelna.

Ostatecznie: Jeżeli chcemy usunąć część ciągu znaków i musimy liczyć od końca, to najłatwiej jest taki ciąg odwrócić, użyć cut’a i odwrócić jeszcze raz. O tak:

 ... | rev | cut -d- -f2- | rev

Trackback URL: http://konrad.bechler.pl/2012/07/manipulacja-ciagami-znakow-w-konsoli-linuksa/trackback/

«

»

Comments

Comment from rozie
Time 3 lipca 2012 at 18:38

Zrobione, ale brzydko.
1. Męczenie kota. Zamiast cat plik.txt | rev mogłeś rev < plik.txt
2. sort | uniq lepiej zapisać jako sort -u – jedno polecenie mniej, dla większych ilości danych jest różnica w wydajności
3. Skoro chciałeś się tylko pozbyć znaków po ostatnim myślniku, to lepiej pewnie użyć regexpa: perl -ne 's/\-[^-]*$//; print "$_\n"' < packages_test.txt Pewnie nie potrzeba angażować perla i wystarczy sed, ale akurat tu wiem, że PCRE działają.

Comment from konrad
Time 3 lipca 2012 at 19:06

Zdarzało mi się robić brzydsze rzeczy :-)

Z pierwszym punktem nie będę dyskutował, bo to kwestia przyzwyczajenia i osobistych preferencji. Oczywiście w przypadku jednorazowych akcji, bo cat jest zdecydowanie mniej wydajny, niż „Twoje” rozwiązanie. Ale perl, szczególnie w połączeniu z regexpami jest dla większości ludzi zdecydowanie mniej przejrzysty, niż rev+cut.

A, no i dzięki za komentarz, wydawało mi się, że piszę głownie w przestrzeń :-)

K.

Comment from rozie
Time 4 lipca 2012 at 18:54

Wiem, też w praktyce często stosuję wersje cat plik | dalsza obróbka. Bardziej chodziło mi o utrwalanie nawyków – jeśli poświęcisz parę minut na optymalizację przed opublikowaniem, to jest szansa, że następnym razem skorzytasz z lepszej wersji, utrwali się i w ogóle. O kilku (nastu/dziesięciu/set) innych osobach, które to przeczytają nie wspominam.

A przejrzystość to też rzecz względna i kwestia przyzwyczajenia. Jak regexpa zwykle przeczytam, tak do cuta bez manuala nie podejdę.

Co do czytania – mam Cię w RSS od jakiegoś czasu. Polecam dodanie się do linuxportal.pl – trochę się ożywi, a na pewno będzie więcej czytelników.

Comment from konrad
Time 5 lipca 2012 at 11:11

Wersja z cat’em jest o tyle „lepsza”, że wygodniej się ją czyta, bo dane plyną sobie z lewej do prawej i nigdzie nie skaczą. A co do „lepszości” metody bez cat’a, to zrobiłem mały teścik i wyszło mi, że jest ona szybsza o około 5% (dla pliku z ponad 600k wpisami), więc – jeżeli nie działamy na jakiejś wyjątkowo mało wydajnej platformie – różnica w wydajności jest średnio zauważalna w amatorskich zastosowaniach. Z drugiej strony… http://partmaps.org/era/unix/award.html#cat ;-)

Przy okazji – z testu wyszło, że Twój regexp z PERLa jest duuuużo szybszy, niż wszystko inne. Z resztą, jak znajdę chwilę, to postram się to poskładać i wrzucić tutaj w formie mini-benchmarka.

Nad linuxportal.pl pomyślę – dzięki za sugestię.

Comment from rozie
Time 5 lipca 2012 at 22:19

Perl jest szybszy zapewne dlatego, że zamiast 6 procesów uruchamiany jest 1. I ogólnie wrzucanie wszystkich języków „interpretowanych” do jednego worka pod względem szybkości jest błędem. Cudzysłów, bo Perl (podobnie jak Ruby czy Python) nie są tak do końca interpretowane. A jak się bawisz w optymalizacje, to tego Perla jeszcze pewnie można nieco przyspieszyć. Np. perl -ne ‚s/\-[^-]*$//o; print „$_\n”‚ – pewnie jakieś pojedyncze procenty szybciej się wykona.

Comment from konrad
Time 7 lipca 2012 at 12:34

Częściowo masz na pewno rację, ale zrobiłem dwa testy:
time perl -ne ‚s/\-[^-]*$//; print „$_\n”‚ ./out/perl1.txt
time awk -F’-‚ ‚{for(i=1;i ./out/awk1.txt
Czas tego drugiego był 3.5x dłuższy, a niby też jeden proces. Chociaż tutaj podejrzewam, że ‚printf’ ma spory narzut czasowy i stąd taka różnica.

Write a comment





*