Sida 1 av 1

Hur få sed att ignorera delar av text?

Postat: 22 jun 2012, 11:55
av Johnny Rosenberg
Exempel:

Kod: Markera allt

$ echo 'printf("a×b=%f",a×b);' | sed 's/×/\*/g'
printf("a*b=%f",a*b);
$
Önskat resultat:

Kod: Markera allt

printf("a×b=%f",a*b);
Det vill säga, hur får jag sed att strunta i det som är inom citattecken? Givetvis kan det finnas alltifrån 0 till ∞ många ”citat” i texten (och det behöver ju inte vara just printf-satser) och de kan befinna sig lite varstans på raden, men alltid i par.

Nästa steg är sedan att förhindra ändringar efter ”//” på en rad och det svåraste av allt (antar jag), att förhindra att sed ändrar saker mellan ”/*” och ”*/”, som ju kan befinna sig på olika rader.

Syfte
Tänkte, i experimentsyfte, försöka skriva ett litet skript som tillåter mig att använda mer läsbart format på C-kod, exempelvis ”Variabel²” istället för ”pow(Variabel,2)”, svenska bokstäver i variabelnamn och så vidare. Skriptet ska då konvertera till giltig C och spara i en ny fil innan gcc tar över och kompilerar den nya filen. Ganska långt kvar på det projektet, om det nu ens blir av, men någonstans måste man börja och detta är mitt första hinder…

Re: Hur få sed att ignorera delar av text?

Postat: 22 jun 2012, 14:13
av ycc
Här kommer bara några snabba förslag. Ta dem för vad de är värda:

- Man kan börja med att byta ut x inom "" mot något kontrolltecken eller kod som inte förekommer i filen. Sedan byter man x mot *, till sist byter man tillbaka kontrolltecknet mot x.

Inte så elegant kanske, men i flera steg blir det lättare att överblicka.

Ibland kan det förenkla att byta ut varje inledande " mot ett kontrolltecken och varje avslutande " mot ett annat kontrolltecken.

- Lookaround är bra att känna till, men kanske inte nödvändigt här
http://www.e-dog.info/t/63/doc/Ubuntu_skript.php

- Har man citat over flera rader kan byta radslut mot kontrolltecken och lagga hela filen pa en rad. Sedan k;r man de byten som behovs. Till sist byter man tillbaka kontrolltecken mot radslut.

OK, det vara bara losa tankar hoppas nagot kan vara anvandbart.

Re: Hur få sed att ignorera delar av text?

Postat: 22 jun 2012, 16:38
av Johnny Rosenberg
ycc skrev:Här kommer bara några snabba förslag. Ta dem för vad de är värda:

- Man kan börja med att byta ut x inom "" mot något kontrolltecken eller kod som inte förekommer i filen. Sedan byter man x mot *, till sist byter man tillbaka kontrolltecknet mot x.

Inte så elegant kanske, men i flera steg blir det lättare att överblicka.
Förmodar att du med ”x” menar ”×” där, de ser ju ganska lika ut med vissa teckensnitt…
Jo, så kan man ju göra, men problemet är ju att det är fler tecken som måste ersättas i så fall, en hel del, faktiskt… Det där med ”×” var ju vara ett litet exempel, jag har ju tänkt att kunna använda en hel del, för C normalt otillåtna tecken, i aritmetiska uttryck och annat, exempelvis åäö i variabelnamn, ”³√()” istället för ”cbrt()” och så vidare.

Kanske blir att man får skriva ett C-program istället, då har man ju maximal kontroll in i minsta detalj, men det kan ju bli rätt jobbigt ändå. Synd att C inte har inbyggt stöd för reguljära uttryck. Om någon känner till något användbart och buggfritt bibliotek för sådant, är det bara att hojta till. Känns som om jag måste lära mig Python. Jag är snart den ende på planeten som inte kan det…

Kan man kompilera Python-program, förresten? Jag vet att man inte behöver, men man kanske vill det ändå, av någon anledning…
ycc skrev:Ibland kan det förenkla att byta ut varje inledande " mot ett kontrolltecken och varje avslutande " mot ett annat kontrolltecken.
Ja, exempelvis kan man ju använda “ respektive ”.
ycc skrev:- Lookaround är bra att känna till, men kanske inte nödvändigt här
http://www.e-dog.info/t/63/doc/Ubuntu_skript.php

- Har man citat over flera rader kan byta radslut mot kontrolltecken och lagga hela filen pa en rad. Sedan k;r man de byten som behovs. Till sist byter man tillbaka kontrolltecken mot radslut.

OK, det vara bara losa tankar hoppas nagot kan vara anvandbart.
Alltid roligt att någon svarar och försöker hjälpa till, vare sig man kommer att använda det eller inte. Även om man inte använder sig av svaren man får, får man ändå ibland någon idé man inte hade fått annars, så det är ju oftast värdefullt i vilket fall.

Re: Hur få sed att ignorera delar av text?

Postat: 22 jun 2012, 19:06
av David Andersson
Johnny Rosenberg skrev: Det vill säga, hur får jag sed att strunta i det som är inom citattecken?
...

Nästa steg är sedan att förhindra ändringar efter ”//” på en rad och det svåraste av allt (antar jag), att förhindra att sed ändrar saker mellan ”/*” och ”*/”, som ju kan befinna sig på olika rader.

Syfte
Tänkte, i experimentsyfte, försöka skriva ett litet skript som tillåter mig att använda mer läsbart format på C-kod, exempelvis ”Variabel²” istället för ”pow(Variabel,2)”, svenska bokstäver i variabelnamn och så vidare.
För detta behöver du en parser. Det är helt opraktiskt att försöka göra det med (individuella) reguljära uttryck. (Reguljära uttryck som bara försöker översätta de delar av språket som skiljer mellan ditt språk och C.)

Ska man kunna skriva c=(a+b)² som ska bli c=pow((a+b),2) ? c=sin(sqt(abs(pi)))² ? Det kan i princip vara hur mycket och hur komplicerat som helst före "²" och det måste parsas med rätt antal parenteser och med rätt prioritering av + - * och / för att rätt uttryck ska hamna i pow().

Med en riktig parser ska du heller inte ha några problem med (delar av) kommentarer i strängar eller (delar) av strängar i kommentarer.

x="en /* ofullständig kommentar i en sträng";

/* en "ofullständig sträng i en kommentar */

Alltså, glöm sed

Re: Hur få sed att ignorera delar av text?

Postat: 22 jun 2012, 23:26
av Johnny Rosenberg
Jo, det kanske blir lite mer komplicerat än man först tänker sig… Det var ju ett stort antal år sedan nu som man läste kompilatorteknik och det blir nog ett hyfsat stort projekt, så jag antar att det är enklast att bara släppa det. Annars verkar det alltså vara C som gäller (eftersom det är det jag kan bäst, därmed inte sagt bra) istället för att försöka ta en och annan genväg som i detta fall troligen blir en rejäl senväg…

Nåja, har en del andra projekt som är klart viktigare, så jag lägger nog ner denna idé, som sagt. Men det hade varit fräckt… ;D

Tack ändå!

Re: Hur få sed att ignorera delar av text?

Postat: 22 jun 2012, 23:57
av David Andersson
Johnny Rosenberg skrev: Nåja, har en del andra projekt som är klart viktigare, så jag lägger nog ner denna idé, som sagt. Men det hade varit fräckt… ;D
Nej, förlåt om mitt svar lät nedslående. Vi kan förenkla det lite.

Om vi struntar i "²" och koncentrerar oss på "×" så behövs inte en full parser; då räcker det med en tokenizer (eller lexer).

Det finns stöd för parsers i många språk, men oftast ska man då skriva en grammatik i en speciell fil, som kompileras till målspråket, som ska kompileras och importeras och länkas till ditt huvudprogram, som anropar parsern. För C är yacc mest känd. Det finns även för java, python och andra.

Det finns stöd för tokenizers i många språk. Det brukar vara enklare att använda. Man kan specificera syntax för tokens och anropa tokenizern i samma program eller script som man sedan använder tokena i. Eller man kan göra en själv. Exempel perl

Kod: Markera allt

#!/usr/bin/perl -0777
use strict;
use warnings;

while(<>) {
    while(1) {
	if(0) {}
	elsif(m/\G("([^"]|\\.)*")/sgc) { print $1; }    # string "..."
	elsif(m/\G('([^']|\\.)*')/sgc) { print $1; }    # string '...'
	elsif(m/\G(\/\/([^\n]*\n))/sgc) { print $1; }   # comment //...\n
	elsif(m/\G(\/\*.*?\*\/)/sgc) { print $1; }      # comment /*...*/
	elsif(m/\G(×)/sgc) { print "*"; }               # pretty multiplication
	elsif(m/\G(.)/sgc) { print $1; }                # anything else
	elsif(m/\G\z/) { last; }                        # end of file
	else { die "Something in the file we didn't think of\n"; }
    }
}
Det är viktigt att varje sökuttryck börjer med \G så att den inte hoppar frammåt i bufferten utan försöker matcha uttrycket precis där föregående matchning slutade. /sgc i slutet behövs också. s=punkt kan matcha nyrad, c=continue, nästa match fortsätter efter denna position (inte börja om från början av bufferten).

Det blir alltså en massa svårläst småkrafs att göra en tokenizer i perl. Försök inte göra om den till en parser. Det kommer bara att orsaka onödigt lidande.

Re: Hur få sed att ignorera delar av text?

Postat: 23 jun 2012, 12:04
av ycc
Tack David, det är ju intressant och riktigt det du skriver.

Jag tycker bara att sed är snabb för grovjobb av textformatering. Den inledande frågeställningen om hur man undviker att sed jobbar på kommentarer och citat är ju nog snabbt löst med regexp.

Tyvärr har jag dock inte hållit på regelbundet med detta på många är. (Det var innan Perl var uppfunnet, haha)

Loop-operatorn t (sed) kan kanske användas.

Kod: Markera allt

$ cat tst
Behåll blanka  här %%%% Byt blanka  mot understrykning här
$ cat tst | sed -r ':a;s/^(.*% [^ ]*) /\1_/;ta'
Behåll blanka här %%%% Byt_blanka__mot_understrykning_här 
Kan man kombinera sed/regexp med ett programmeringsspråk som t.ex. PHP (preg-replace heter sed-substitute i PHP) kan man ju loopa och formatera även där.

Det man inte vill skall påverkas kan man ju skriva ut till en fil och byta deras ursprungliga platser mot kontrolltecken etc

Jag ville prova att köra denna websida genom en översättare
http://e-dog.info/t/63/doc/Ubuntu_insta ... ?rand=1126

översättaren korrumperade dock JavaScript, PHP, länkar etc

Det funkade bra att maskera ut text med PHP/regexpar

Det tog dock processorkraft att köra substitutionskommandon genom hela filer. I alla fall de regexpar jag fick till.

(resultatet blev så här, "översättningen" är ju bara en test, förstås
http://e-dog.info/t/63/doc/Ubuntu_insta ... rand=36141 )

En parser är ju en mer avancerad konstruktion. Men jag önskar att jag verkligen kunde regexpar och sed i detalj, det går att underlätta mycket råjobb med det.

Re: Hur få sed att ignorera delar av text?

Postat: 24 jun 2012, 20:53
av David Andersson
ycc skrev: Den inledande frågeställningen om hur man undviker att sed jobbar på kommentarer och citat är ju nog snabbt löst med regexp.
Jag är ledsen men regexp klarar helt enkelt inte av det.
ycc skrev:

Kod: Markera allt

$ cat tst
Behåll blanka  här %%%% Byt blanka  mot understrykning här
$ cat tst | sed -r ':a;s/^(.*% [^ ]*) /\1_/;ta'
Behåll blanka här %%%% Byt_blanka__mot_understrykning_här 
Om man ska jämföra med strängar. Byt ut % mot " i exemplet ovan:

Kod: Markera allt

 inte här " understyk här " inte här " undestryk här " inte här 
Man måste helt enkelt räkna varje " ända från början av filen. Processen måste ta ställning till varje tecken. Hoppar man över ett enda tecken kan det bli fel. Lägg därtill att om " föregås av \ ska den inte räknas.

Kod: Markera allt

 inte här " understyk här \" undestryk här " inte här 
Om vi bara har vanliga strängar " " att ta hänsyn till. Då går det att göra ett igenkännande regexp:
/\A([^"]+|"([^"\\]|\\.)*")*x/
Det hittar ett x som garanterat inte finns i en sträng. Det går t.o.m att göra ett uttryck för sed som byter ut x mot *. Men vad göra om det finns flera x?

Så redan här, när vi bara har " att ta hänsyn till, om det ska göras nåt med varje förekomst av x som inte är i en sträng så blir sed och regexp helt otillräckligt.

Men det är värre än så

Vi har inte bara ". Vi har ', /*, */, //, och nyrad.
" ska inte räknas inom ' '.
' ska inte räknas inom " ".
Varken " eller ' ska räknas inom /* */ eller // nyrad.
// ska inte räknas inom /* */.
/* ska inte räknas inom // nyrad.
Varken /* och // ska inte räknas inom " " och ' '.
Varken " och ' ska räknas direkt efter \ .

Kanske att det finns en guru i världen som kan göre ett igenkännande regexp som klarar allt detta, men det blir väldigt komplicerat och obegripligt. Och den kommer inte att klara att byta ut flera förekomster av x till *, inte i sed.

Perl-snutten i tidigare inlägg tittar på vartenda tecken i hela filen. Den klarar att byta ut flera förekomster av x till *. Den byter inte i strängar (' ' och " ") och kommentarer (/* */ och // nyrad). Den låter sig inte luras av /* och // i strängar eller av ' och " i kommentarer. Den använder regexp, men i dess huvudstruktur är den en tokenizer. (Flera små regexp används för att känna igen olika tokens.)

Och som sagt. När vi inför ² i ekvationen räcker inte en tokenizer, då måste vi ha en parser.

Utvikning

Prisjämförelsesajter som Prisjakt och Pricerunner, hur vet de vilka priser olika varor har på olika säljsajter? Troligen (förhoppningsvis) har säljsajterna APIn. Om inte så får prisjämförelsesajterna ha program som parsar html-koden för att hitta varans namn och pris och liknande. Säg att htmlkoden för en bok ser ut så här

Kod: Markera allt

<div class="title"> Liftarens guide till Galaxen </div>
<div class="price"> 49 kronor </div>
<div class="sample"> "Adjö och tack för fisken" </div>
Tänk om de använder regexp för att hitta priset
/class="price".*([0-9]+)/

Tänk om priset har ändrats och det gamla priset ligger kvar i en html-kommentar:

Kod: Markera allt

<div class="title"> Liftarens guide till Galaxen </div>
<!-- Gammalt pris <div class="price"> 59 kronor </div>-->
<div class="price"> 49 kronor </div>
<div class="sample"> "Adjö och tack för fisken" </div>
Vilket pris kommer då att visas på sajten? Vad händer om nåt som ser ut som html-fragment förekommer i texten:

Kod: Markera allt

<div class="title"> En konstig roman av David </div>
<div class="price"> 89 kronor </div>
<div class="sample"> Hon: Jag älskar dej! Han: class="price". Hon: Va? </div>

Re: Hur få sed att ignorera delar av text?

Postat: 25 jun 2012, 07:24
av ycc
Tack David, dina inlägg håller alltid hög klass, tekniskt och litterärt, samt har en trevlig ton, tycker jag. Kul att läsa.

Jag är tyvärr lite upptagen just nu och kan just nu bara läsa snabbt vad du skriver. Jag sitter inte heller så jag kan köra regexp.

Men jag frågar dig. Visst måste det vara möjligt att, med sed/regexp på ett enkelt sätt undvika att en substitution arbetar på citat? Jag håller inte på med detta dagligen numera, men ibland måste jag formatera text och jag har ofta tyckt att kombinationen PHP/preg_replace/reg-exp verkat nastan oövervinnelig, haha ;) (Jag kor PHP eftersom det ar praktiskt att kora pa en server. Det finns sakert andra motsvarande kombinationer.)

Här är filen vi arbetar på

Kod: Markera allt

"byt inte här"  byt och manipulera här  "här skall inget ändras" i detta parti skall man ändra
Det får bli pseudokod:

1. Byt alla förekomster av ".*?" mot ett kontrolltecken, t.ex. ctrl/G, skriv samtidigt ut det utbytta materialet i en fil, t.ex. ett citat på varje rad

den ursprungliga filen blir då

Kod: Markera allt

ctrl/G  byt och manipulera här ctrl/G i detta parti skall man ändra
Den skapade filen blir

Kod: Markera allt

"byt inte här"
"här skall inget ändras"
2. Gör de nödvändiga manipulationerna på den ursprungliga filen.

3. Byt slutligen tillbaka varje ctrl/G mot motsvarande rad i den fil som skapades och innehåller det material som skulle skyddas mot forandringar.

Jag säger inte att det är elegant. Men jag tycker det borde vara en metod som går mycket kvickt att genomföra. Det var i grunden så jag gjorde när jag ville skydda CSS/JavaScript/PHP/länkar etc pa en websida från att korrumperas av Google translate.

Att arbeta i flera steg är ju inte så effektivt eller elegant. Men det är mycket praktiskt när man felsöker. Jag medger att regexpar kan vara felbenägna, men det beror bara på att jag inte tränar dem dagligen, haha