Mam plik wejściowy z niektórymi sekcjami, które są oznaczone znacznikami początkowym i końcowym, na przykład:
line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
Chcę zastosować transformację do tego pliku, tak aby wiersze X, Y, Z były filtrowane przez niektóre polecenia ( nl
na przykład), ale reszta linii przechodzi przez niezmienione. Zauważ, że nl
(linie liczbowe) akumulują stan w poprzek linii, więc nie jest to transformacja statyczna, która jest stosowana do każdej z linii X, Y, Z. ( Edycja : wskazano, że nl
może działać w trybie, który nie wymaga stanu akumulacji, ale używam jedynie nl
jako przykładu w celu uproszczenia pytania. W rzeczywistości polecenie jest bardziej złożonym niestandardowym skryptem. Tak naprawdę wyglądam for to ogólne rozwiązanie problemu zastosowania standardowego filtra do podsekcji pliku wejściowego )
Dane wyjściowe powinny wyglądać następująco:
line A
line B
1 line X
2 line Y
3 line Z
line C
line D
W pliku może znajdować się kilka takich sekcji, które wymagają transformacji.
Aktualizacja 2 Nie określiłem pierwotnie, co powinno się stać, jeśli jest więcej niż jedna sekcja, na przykład:
line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
@@inline-code-start
line L
line M
line N
@@inline-code-end
Oczekuję, że stan będzie musiał zostać utrzymany tylko w ramach danej sekcji, dając:
line A
line B
1 line X
2 line Y
3 line Z
line C
line D
1 line L
2 line M
3 line N
ale myślę, że interpretacja problemu jako wymagającego zachowania stanu w sekcjach jest poprawna i przydatna w wielu kontekstach.
Zakończ aktualizację 2
Moją pierwszą myślą jest zbudowanie prostej maszyny stanów, która śledzi, w której sekcji się znajdujemy:
#!/usr/bin/bash
while read line
do
if [[ $line == @@inline-code-start* ]]
then
active=true
elif [[ $line == @@inline-code-end* ]]
then
active=false
elif [[ $active = true ]]
then
# pipe
echo $line | nl
else
# output
echo $line
fi
done
Z którym biegam:
cat test-inline-codify | ./inline-codify
To nie działa, ponieważ każde połączenie z nl
jest niezależne, więc numery linii nie zwiększają się:
line A
line B
1 line X
1 line Y
1 line Z
line C
line D
Następną próbą było użycie fifo:
#!/usr/bin/bash
mkfifo myfifo
nl < myfifo &
while read line
do
if [[ $line == @@inline-code-start* ]]
then
active=true
elif [[ $line == @@inline-code-end* ]]
then
active=false
elif [[ $active = true ]]
then
# pipe
echo $line > myfifo
else
# output
echo $line
fi
done
rm myfifo
To daje poprawne wyjście, ale w niewłaściwej kolejności:
line A
line B
line C
line D
1 line 1
2 line 2
3 line 3
Prawdopodobnie dzieje się trochę buforowania.
Czy wszystko źle robię? To wydaje się dość ogólnym problemem. Wydaje mi się, że powinien istnieć prosty rurociąg, który by to rozwiązał.
źródło
nl
nie musi gromadzić stanu . Spójrz nanl -d
i sprawdzićman
/info
stron zawierających informacje onl
„s sekcja separatora .nl
jako przykładowego filtra. Pomyślałem, że uprości to pytanie, wyjaśniając szczegóły tego, co dokładnie robi filtr, ale prawdopodobnie spowodowałem więcej zamieszania. W rzeczywistości filtruję podsekcję za pomocą zakreślacza kodu, aby uzyskać domowy generator statycznych blogów. Obecnie używam GNUsource-highlight
, ale to może się zmienić i mogę dodać więcej filtrów, takich jak formater.Odpowiedzi:
Zgadzam się z tobą - to prawdopodobnie jest to ogólny problem. Jednak niektóre popularne narzędzia mają pewne funkcje do obsługi tego.
nl
nl
, na przykład, dzieli dane wejściowe na strony logiczne, które są-d
eliminowane przez dwuznakowy separator sekcji . Trzy zdarzenia na linii same w sobie wskazują początek kursu , dwa ciała i jeden stopkę . Zastępuje wszystkie znalezione na wejściu puste linie wyjściowe - które są jedynymi pustymi liniami, jakie kiedykolwiek drukujeZmieniłem twój przykład, aby dołączyć inną sekcję i wstawić go
./infile
. Wygląda to tak:Następnie wykonałem następujące czynności:
nl
można powiedzieć, aby gromadził stan na logicznych stronach, ale domyślnie tak nie jest. Zamiast tego numeruje wiersze danych wejściowych według stylów i według sekcji . Tak więc-ha
środki ponumerować wszystkie nagłówki wierszy i-bn
oznacza brak linii nadwozia - jak zaczyna się w organizmie stanie.Dopóki się tego nie nauczyłem, używałem
nl
dowolnego wejścia, ale po uświadomieniu sobie, żenl
może to zniekształcić dane wyjściowe zgodnie z domyślnym-d
eliminatorem\:
, nauczyłem się być bardziej ostrożnym i zacząłem używaćgrep -nF ''
zamiast tego nieprzetestowanego wejścia. Ale kolejną lekcją wyciągniętą tego dnia było to, żenl
można bardzo użytecznie zastosować pod innymi względami - takimi jak ten - jeśli tylko zmodyfikujesz jego dane wejściowe tylko nieznacznie - tak jak jased
powyżej.WYNIK
Oto kilka więcej informacji
nl
- czy zauważysz powyżej, jak wszystkie wiersze oprócz ponumerowanych zaczynają się od spacji? Kiedynl
linie liczb wstawiają określoną liczbę znaków do nagłówka każdego z nich. W przypadku tych wierszy nie jest numerowany - nawet puste - zawsze dopasowuje wcięcie, wstawiając ( idth-w
count +-s
eparator len) * spacje na początku nienumerowanych linii. Pozwala to na dokładne odtworzenie treści nienumerowanej przez porównanie jej z treścią numerowaną - przy niewielkim wysiłku. Jeśli weźmiesz pod uwagę, żenl
podzieli on twoje dane wejściowe na logiczne sekcje i że możesz wstawić dowolne-s
ciągi na początku każdej linii, którą numeruje, to całkiem łatwo jest obsłużyć wynik:Powyższe wydruki ...
GNU ANTYLOPA
sed
Jeśli
nl
nie jest twoją aplikacją docelową, GNUsed
możee
wykonać dowolne polecenie powłoki w zależności od dopasowania.Powyżej
sed
zbiera dane wejściowe w przestrzeni wzorów, dopóki nie będzie wystarczające, aby pomyślnie przejść podstawienieT
est i przestaćb
biegać z powrotem do:l
abla. Kiedy tak się dzieje, wykonujee
polecenianl
wejściowe reprezentowane jako<<
dokument tutaj dla całej reszty przestrzeni wzorcowej.Przepływ pracy wygląda następująco:
/^@@.*start$/!b
^
cała linia$
ma!
nie/
pasuje/
powyższy wzór, to jestb
ranczerskich z skryptu i autoprinted - więc od tej chwili pracujemy tylko z serią linii, która rozpoczęła się z wzoru.s//nl <<\\@@/
s//
pole/
oznacza ostatni adres, którysed
próbowano dopasować - więc to polecenie zastępuje zamiast tego całą@@.*start
linięnl <<\\@@
.:l;N
:
Polecenie definiuje etykietę oddział - tu ustawić jeden o nazwie:l
Abel. PolecenieN
ext dołącza następny wiersz danych wejściowych do obszaru wzorów, po którym\n
następuje znak ewline. Jest to jeden z niewielu sposobów, aby uzyskać\n
ewline wsed
przestrzeni wzorów -\n
postać ewline jest pewnym ogranicznikiem dlased
der, który robił to jakiś czas.s/\(\n@@\)[^\n]*end$/\1/
s///
ubstytucja może się powieść tylko po napotkaniu początku i tylko przy pierwszym wystąpieniu linii końcowej . Będzie działał tylko na przestrzeni wzorów, w której\n
natychmiast po ostatniej linii ewline zostanie@@.*end
zaznaczony sam koniec$
przestrzeni wzorów. Kiedy działa, zastępuje cały dopasowany ciąg\1
pierwszą\(
grupą\)
lub\n@@
.Tl
T
gałęzie dowodzenia est na etykiecie (jeżeli istnieje) , czy udane podstawienie nie nastąpiło od czasu ostatniego linia wejściowa została wciągnięta w przestrzeń wzoru (tak jak ja w /N
) . Oznacza to, że za każdym razem, gdy\n
ewline jest dodawana do przestrzeni wzorów, która nie pasuje do ogranicznika końcowego,T
polecenie est kończy się niepowodzeniem i rozgałęzia się z powrotem do:l
abla, co powodujesed
ciągnięcieN
linii zewnętrznej i zapętlanie aż do pomyślnego zakończenia .e
Gdy podstawienie na mecz końcowego jest udany, a skrypt nie oddział z powrotem dla nieudanej
T
est,sed
będąe
xecute polecenie, którel
ooks tak:Możesz to zobaczyć, edytując ostatnią linię, aby wyglądała
Tl;l;e
.Drukuje:
while ... read
Ostatnim sposobem, a może najprostszym, jest użycie
while read
pętli, ale nie bez powodu. Powłoka - (szczególniebash
powłoka) - jest zazwyczaj dość fatalna w obsłudze dużych ilości lub stałych strumieni. To też ma sens - zadaniem powłoki jest obsługa znaków po znaku i wywoływanie innych poleceń, które mogą obsłużyć większe rzeczy.Ale co ważne, jego rola polega na tym, że powłoka nie może
read
przesadzać z danymi wejściowymi - jest określona, aby nie buforować danych wejściowych lub wyjściowych do momentu, w którym zużywa tak dużo lub nie przekazuje wystarczająco dużo czasu, aby wywoływać polecenia, których nie ma - do bajtu.read
Stanowi więc doskonały test wejściowy - doreturn
informacji o tym, czy jest jeszcze wejście i powinieneś wywołać następne polecenie, aby je odczytać - ale ogólnie nie jest to najlepsza droga.Oto przykład, jednak, jak można wykorzystać
read
i inne polecenia do wejścia procesu synchronizacji:Pierwszą rzeczą, która dzieje się dla każdej iteracji, jest
read
ciągnięcie linii. Jeśli się powiedzie, oznacza to, że pętla nie uderzyła jeszcze w EOF, a więc wcase
dopasowaniu ogranicznika początkowegodo
blok jest natychmiast wykonywany. W przeciwnym razieprintf
drukuje$line
goread
ised
nazywa się.sed
będziep
rintować każdą linię, aż napotka znacznik początkowy - kiedyq
całkowicie wykorzysta wejście.-u
Przełącznik nbuffered jest konieczne dla GNUsed
ponieważ może buforować dość łapczywie inaczej, ale - według specyfikacji - inne POSIXsed
s powinno działać bez szczególną uwagę - tak długo, jak<infile
jest zwykłym plikiem.Gdy pierwsze
sed
q
uituje, powłoka wykonujedo
blok pętli - która wywołuje kolejną,sed
która wypisuje każdą linię, aż napotka znacznik końca . Potokuje dane wyjściowe dopaste
, ponieważ wypisuje numery linii w osobnych wierszach. Lubię to:paste
następnie wkleja je razem do:
postaci, a cały wynik wygląda następująco:To tylko przykłady - tutaj można wykonać wszystko w teście lub wykonać bloki, ale pierwsze narzędzie nie może zużywać zbyt wiele danych wejściowych.
Wszystkie zaangażowane narzędzia odczytują te same dane wejściowe - i drukują wyniki - każde z osobna. Tego rodzaju rzeczy może być trudno się zawiesić - ponieważ różne narzędzia będzie buforować więcej niż inni - ale można ogólnie polegać na
dd
,head
ised
zrobić to, co trzeba (choć dla GNUsed
, trzeba CLI-switch) i zawsze powinieneś być w stanie polegaćread
- ponieważ z natury jest bardzo powolny . I dlatego powyższa pętla wywołuje ją tylko raz na blok wejściowy.źródło
sed
przykład, który podałeś i działa, ale NAPRAWDĘ mam problem z odczytaniem składni. (mój sed jest dość słaby i zwykle ogranicza się do s / findthis / replaceethis / g. Będę musiał usiąść i naprawdę zrozumieć sed.)Jedną z możliwości jest zrobienie tego za pomocą edytora tekstu vim. Może potokować dowolne sekcje za pomocą poleceń powłoki.
Jednym ze sposobów na to jest użycie numerów linii za pomocą
:4,6!nl
. To polecenie ex uruchomi nl w liniach 4-6 włącznie, osiągając to, co chcesz na przykładowym wejściu.Innym, bardziej interaktywnym sposobem jest wybranie odpowiednich linii za pomocą trybu wyboru linii (Shift-V) i klawiszy strzałek lub wyszukiwania, a następnie za pomocą
:!nl
. Pełna sekwencja poleceń dla twojego przykładowego wejścia może byćNie jest to zbyt dobrze dostosowane do automatyzacji (lepsze są odpowiedzi z użyciem np. Sed), ale w przypadku edycji jednorazowych bardzo przydatne jest to, że nie trzeba uciekać się do 20-liniowych skryptów powłoki.
Jeśli nie znasz vi (m), powinieneś przynajmniej wiedzieć, że po tych zmianach możesz zapisać plik za pomocą
:wq
.źródło
HOME=$(pwd) vim -c 'call Mf()' f
. Jeśli używasz xargs, możesz chcieć użyć gvim na dedykowanym serwerze xserver, aby nie uszkodzić twojego tty (vnc jest niezależny od karty graficznej i może być monitorowany).Najprostszym rozwiązaniem, jakie mogę wymyślić, jest nieużywanie,
nl
ale samodzielne policzenie wierszy:Następnie uruchom go na pliku:
źródło
Jeśli Twoim celem jest wysłanie całego bloku kodu do pojedynczej instancji procesu, możesz zgromadzić linie i opóźnić potokowanie, aż dojdziesz do końca bloku kodu:
Powoduje to utworzenie pliku wejściowego, który trzykrotnie powtarza przypadek testowy:
Zrobić coś innego z bloku kodu, np odwrócić, a następnie numer, tylko rury to przez coś innego:
echo -E "${acc:1}" | tac | nl
. Wynik:Lub liczba słów
echo -E "${acc:1}" | wc
:źródło
Edytuj dodała opcję definiowania filtra dostarczonego przez użytkownika
Domyślnie filtr to „nl”. Aby zmienić filtr, użyj opcji „-p” za pomocą polecenia podanego przez użytkownika:
lub
Ten ostatni filtr wyświetli:
Aktualizacja 1 Użycie IPC :: Open2 ma problemy ze skalowaniem: przekroczenie rozmiaru bufora może spowodować zablokowanie. (w mojej maszynie buforowany rozmiar rury, jeśli 64K odpowiada 10_000 x „linia Y”).
Jeśli potrzebujemy większych rzeczy (czy potrzebujemy więcej 10000 „linii Y”):
(1) zainstaluj i użyj
use Forks::Super 'open2';
(2) lub zamień funkcję pipeit na:
źródło
$/
is
flagę), a użyciee
flagi do wykonania rzeczywistego wywołania polecenia zewnętrznego. Naprawdę podoba mi się drugi przykład (ascii art)!/s
= („.” oznacza(.|\n)
);$/
redefiniuje separator rejestru.To praca dla awk.
Kiedy skrypt widzi znacznik początkowy, zauważa, że powinien rozpocząć pipowanie
nl
. Gdypipe
zmienna ma wartość true (niezerowa), dane wyjściowe są przesyłane donl
polecenia; gdy zmienna ma wartość false (nieustawiona lub zero), dane wyjściowe są drukowane bezpośrednio. Polecenie potokowe jest rozwidlane przy pierwszym napotkaniu konstrukcji potoku dla każdego ciągu polecenia. Kolejne oceny operatora potoku z tym samym łańcuchem ponownie wykorzystują istniejącą potok; inna wartość ciągu utworzyłaby inny potok.close
Zamyka się rurę do danego łańcucha poleceń.Jest to zasadniczo ta sama logika, co skrypt powłoki przy użyciu nazwanego potoku, ale dużo łatwiej jest przeliterować, a ścisła logika jest wykonana poprawnie. Musisz zamknąć potok we właściwym czasie, aby
nl
polecenie zakończyło się, opróżniając jego bufory. Twój skrypt faktycznie zamyka potok zbyt wcześnie: potok jest zamykany, gdy tylkoecho $line >myfifo
zakończy się wykonywanie pierwszego . Jednaknl
polecenie widzi koniec pliku tylko wtedy, gdy otrzyma wycinek czasu przed następnym uruchomieniem skryptuecho $line >myfifo
. Jeśli masz dużą ilość danych lub dodajeszsleep 1
po zapisaniumyfifo
, zobaczysz, żenl
przetwarza tylko pierwszą linię lub pierwszą szybką wiązkę linii, a następnie kończy działanie, ponieważ widzi koniec danych wejściowych.Korzystając ze swojej struktury, musisz trzymać rurkę otwartą, dopóki jej nie będziesz już potrzebować. Musisz mieć pojedyncze przekierowanie wyjścia do potoku.
(Skorzystałem również z okazji, aby dodać poprawne cytowanie i tym podobne - zobacz Dlaczego mój skrypt powłoki dusi się na białych znakach lub innych znakach specjalnych? )
Jeśli to robisz, równie dobrze możesz użyć potoku zamiast nazwanego potoku.
źródło
do
. (Nie mam tutaj przedstawiciela, aby dokonać drobnej edycji.)OK, po pierwsze; rozumiem , że nie szukasz sposobu numerowania linii w sekcjach pliku. Ponieważ nie podałeś rzeczywistego przykładu tego, jaki może być Twój filtr (inny niż
nl
), załóżmy, że taktzn. przekonwertuj tekst na wielkie litery; więc dla wejścia
chcesz wynik
Oto moje pierwsze przybliżenie rozwiązania:
gdzie spacje przed
@@
ciągami znaków i blisko końca ostatniego wiersza to tabulatory. Pamiętaj, że używamnl
do własnych celów . (Oczywiście robię to, aby rozwiązać Twój problem, ale nie po to, aby uzyskać dane wyjściowe z numerami wierszy).To numeruje linie wejścia, dzięki czemu możemy rozdzielić je na znaczniki sekcji i wiedzieć, jak je później złożyć ponownie. Główny korpus pętli opiera się na pierwszej próbie, biorąc pod uwagę fakt, że znaczniki sekcji mają na sobie numery linii. Dzieli dane wejściowe na dwa pliki:
file0
(nieaktywne; nie w sekcji) ifile1
(aktywne; w sekcji). Tak wyglądają powyższe dane wejściowe:Potem biegniemy
file1
(co jest konkatenacją wszystkich linii w sekcji) przez filtr wielkich liter; połącz to z niefiltrowanymi liniami poza sekcją; sortuj, aby przywrócić je do pierwotnej kolejności; a następnie zdejmij numery linii. Daje to wynik pokazany u góry mojej odpowiedzi.Zakłada się, że Twój filtr pozostawia numery linii w spokoju. Jeśli tak nie jest (np. Jeśli wstawia lub usuwa znaki na początku wiersza), to uważam, że to ogólne podejście może być nadal stosowane, ale będzie wymagało nieco trudniejszego kodowania.
źródło
nl
już tam wykonuje większość pracy - po to jest jego-d
opcja eliminatora.Skrypt powłoki, który używa sed do wyprowadzania fragmentów niewyznaczonych linii i dostarczania określonych fragmentów linii do programu filtrującego:
Napisałem ten skrypt do pliku o nazwie detagger.sh i wykorzystał je jako tak:
./detagger.sh infile.txt
. Utworzyłem osobny plik filter.sh, aby naśladować funkcję filtrowania w pytaniu:Ale operację filtrowania można zmienić w kodzie.
Próbowałem za tym podążać za ogólnym rozwiązaniem , aby operacje takie jak linie numeracyjne nie wymagały dodatkowego / wewnętrznego zliczania. Skrypt przeprowadza pewne podstawowe sprawdzanie, aby zobaczyć, czy znaczniki demarkatora są w parach, i nie obsługuje w pełni z wdziękiem tagów zagnieżdżonych.
źródło
Dzięki za wszystkie świetne pomysły. Wymyśliłem własne rozwiązanie, śledząc podsekcję w pliku tymczasowym i przesyłając wszystko naraz do mojego zewnętrznego polecenia. Jest to bardzo podobne do tego, co sugerował Supr (ale ze zmienną powłoki zamiast pliku tymczasowego). Poza tym bardzo podoba mi się pomysł użycia sed, ale dla mnie ta składnia wydaje się nieco przesadzona.
Moje rozwiązanie:
(Używam
nl
tylko jako przykładowego filtra)Wolałbym nie mieć do czynienia z zarządzaniem plikami tymczasowymi, ale rozumiem, że zmienne powłoki mogą mieć raczej niskie limity wielkości i nie znam żadnej konstrukcji bash, która działałaby jak plik tymczasowy, ale znika automatycznie, gdy proces się kończy.
źródło
M
,N
iO
byłyby policzone4
,5
i6
. To nie robi tego. Moja odpowiedź brzmi (poza tym, że w obecnym wcieleniu nie działanl
jako filtr). Jeśli ta odpowiedź daje pożądany wynik, to co miałeś na myśli przez „akumuluj stan w poprzek linii”? Czy chodziło Ci o to, że chcesz zachować stan tylko przez każdą sekcję, ale nie między sekcjami? (Dlaczego nie podałeś przykładu z wieloma sekcjami w swoim pytaniu?)nl -p
aby uzyskaćM,N,O==4,5,6
.