awk: mezőelválasztóban aposztóf

Sziasztok.

Adott egy string:

'regwe', 'khotkml', 'jgoij 43iojt'

Ezt szeretném cat-tal, grep-pel vagy tail-lal csővezetéken ráküldeni az awk-ra. A string fájlban van, soronként van belőle pár száz.

A problémám az, hogy ha megadom az awk-nak a mezőelválasztót, akkor syntax jelentkezik, ami jogos...

Kipróbáltam ezt is:
{FS="\', \'"}
Ismét hiba.

Valamikor egyszer megoldottam, de elfelejtettem hogyan.
Kösz a segítséget előre is.

(Gondolkodtam azon, hogy split-tel szétdartabololom és csinálok egy tömböt, de ágyúval lőnék verébre.)

Hozzászólások

Pedig a splitel szetdarabolas es tombbe rakas nem ordogtol valo. :)

Udv.

Ha a ', ' sorozatot használnád "mezőelválasztónak", nem lenne jó, mivel a sor első és utolsó karaktere (egy-egy ') bennmaradna.
Inkább egy sed-del távolítanám el az oda nem illő karaktereket:

$  cat data
'regwe', 'khotkml', 'jgoij 43iojt'
'regwe', 'khotkml', 'jgoij 43iojt'
'regwe', 'khotkml', 'jgoij 43iojt'

$ sed "s, *',,g" data | awk -F, '{print "1="$1"\n2="$2"\n3="$3"\n"}'
1=regwe
2=khotkml
3=jgoij 43iojt

1=regwe
2=khotkml
3=jgoij 43iojt

1=regwe
2=khotkml
3=jgoij 43iojt

Igen, erre gondoltam már, csak nem tehetem meg. Van a sorban más string is. Legkorrektebb, ha adom a konkrét sort, ez egy sql adatbázis egyik sora:

INSERT INTO `honlap_content` (`id`, `asset_id`, `title`, `alias`, `introtext`, `fulltext`, `state`, `catid`, `created`, `created_by`, `created_by_alias`, `modified`, `modified_by`, `checked_out`, `checked_out_time`, `publish_up`, `publish_down`, `images`, `urls`, `attribs`, `version`, `ordering`, `metakey`, `metadesc`, `access`, `hits`, `metadata`, `featured`, `language`, `xreference`) VALUES

...ezek a mezőnevek, a `fulltext` mezőben van a html kód, ami bizony sok-sok vesszőt is tartalmaz, majd utána van egy space is.
(', ' string nem szerepel a html kódban)

Itt a példasor:

(440, 1763, 'VIII. 4.', 'viii-4', 'VIII. 4.\r\nIde kerülnek olyan monatok, melyekben vesszők is vannak, így bonyolultabbá válik az élet.\r\n\r\n', '', 1, 77, '2016-08-04 14:41:00', 41, '', '2016-10-17 16:48:25', 41, 0, '0000-00-00 00:00:00', '2016-10-17 14:41:14', '0000-00-00 00:00:00', '{\"image_intro\":\"\",\"float_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"image_fulltext\":\"\",\"float_fulltext\":\"\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\"}', '{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}', '{\"show_title\":\"\",\"link_titles\":\"\",\"show_tags\":\"\",\"show_intro\":\"\",\"info_block_position\":\"\",\"info_block_show_title\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_layout\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}', 3, 9, '', '', 1, 344, '{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\",\"marker\":\"\"}', 0, '*', ''),

joomla...

Ha elárulnád, mi a feladat, kedvtelésből akár meg is írnám. Jó, van egy ilyen inputod. Ami nincs aposztrofok között, például a 440 és az 1763, az is kell? Aztán mit csináljunk ezzel? Mondjuk szétszedtem elemekre. És utána? Keressünk az elemek között? Ki kell írni valahanyadik elemet? Vagy?

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Kiexportáltam egy régi adatbázist és egy újat. Volt egy kis sqlinject, amolyan támadásféle. A cikk címe és a találati értéke érdekelne csak, hogy korrigáljam vele a sértetlen régi adatbázist, majd a 30-40 új cikkel kézzel kiegészítve megcsináljam a leendőt.
Azt hittem, bashban nem fog ki rajtam semmi, csak hát az élet nem ilyen..

Hát akkor légy kedves máskor pontosabban definiálni a feladatot :)
Amúgy Nyosigomboccal értek egyet, eleve úgy kellene a lekérdezést, hogy könnyen feldolgozható legyen, akár úgy, ahogy bucko írta, hogy egy olyan karaktert használsz elválasztónak, amely nem fordul elő, talán a | ("pipe") jó is.
Sőt, nem tudom, mit akarsz még a sed/awk/grep-pel, de azt a feladatot akár az adatbázis lekérdezésével is el lehetne intézni (persze nyilván csak akkor, ha olyan a feladat).

Nem használok phpmyadmin-t. Nemigen értem, hogy pontosan mit szeretnél. Miért nem írod meg rendesen az SQL-lekérdezést?

Szóval a javaslatom: az eredeti problémádat oszd meg (minden szükséges infóval), és akkor hátha tudunk jobban segíteni. Ezen hozzászólásodból úgy érzem, ez inkább adatbázis-bűvészkedés lesz, mintsem awk-mágia.
Ugye tudod, hogy két különböző adatbázist is össze tudsz hozni (pl. ez egy kiindulásnak jó lehet)?

Ez esetleg nem használható?

cat string1 | sed s/\ /SZOKOZ/g | sed s/,SZOKOZ/ELVALASZTO/g | sed s/SZOKOZ/" "/g | awk -F "ELVALASZTO" '{print $3}'

Az első sed-ben a \ után van egy szóköz, amit talán a forummotor nem vesz figyelembe,
de talán így:

cat string1 | sed s/\ /SZOKOZ/g | sed s/,SZOKOZ/ELVALASZTO/g | sed s/SZOKOZ/" "/g | awk -F "ELVALASZTO" '{print $3}'

Kösz, az ötletet, én még csak tanulgatom ezeket.
Kipróbáltam egy seddel, igazad van, megoldható, de valamiért van különbség:

nff@sedre ~ $ cat string1 | sed s/\ /SZOKOZ/g | sed s/,SZOKOZ/ELVALASZTO/g | sed s/SZOKOZ/" "/g | awk -F "ELVALASZTO" '{print $3}'
'jgoij 43iojt'
nff@sedre ~ $ cat string1 | sed 's/\ /SZOKOZ/g; s/,SZOKOZ/ELVALASZTO/g; s/SZOKOZ/" "/g' | awk -F "ELVALASZTO" '{print $3}'
'jgoij" "43iojt'
nff@sedre ~ $ cat string1 | sed 's/\ /SZOKOZ/g; s/,SZOKOZ/ELVALASZTO/g; s/SZOKOZ/ /g' | awk -F "ELVALASZTO" '{print $3}'
'jgoij 43iojt'

Vaalószínűleg azért mert ha egy seddel csinálom, akkor azt aposztrofok közé kell tennem.

Az aposztrof a shelltől elzár mindent, így ami közte van, azt a sed literálisként megkapja. Éppen ezért, ha használsz aposztrofot, a regexben sem kell már a szóközt escape-elni, így a backslash-t (\) is szedd ki onnan!

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Nézd a topic vége felé, ezt a problémát hogyan oldottam meg. Ki kell mazsolázni aposztroffal együtt, aztán elejét, végét le kell vágni egy hentesbárddal! :) A cat nem kell oda, van a shell-ben stdin átirányítás. Ne fusson, csak az erőforrást viszi.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Mert elzárva hibát adott:
nff@sedre ~ $ sed s/\',\ \'/ELVALASZTO/g string1
'regweELVALASZTOkhotkmlELVALASZTOjgoij 43iojt'
nff@sedre ~ $ sed 's/', '/ELVALASZTO/g' string1
sed: -e kifejezés #1, karakter 3: befejezetlen „s” parancs

Aztán rájöttem, hogy a szóközt mégiscsak escape-elni kell, úgy már jó lett:

nff@sedre ~ $ sed 's/',\ '/ELVALASZTO/g' string1
'regwe'ELVALASZTO'khotkml'ELVALASZTO'jgoij 43iojt'
nff@sedre ~ $ sed 's/',\ '/ELVALASZTO/g' string1 | awk -F "ELVALASZTO" '{print $3}'
'jgoij 43iojt'

Elmondom, én hogy csinálnám, de ha nincs kedved rááldozni egy órácskát sem, akkor nem fog menni.

Csak awk-t használnék, semmi értelme grep-nek, sed-nek előtte, ha az awk mindent tud, amit a másik kettő. Az elején, tehát a BEGIN után a field separator-t üres stringnek definiálnám.

Belül soronként végigmásznék egyesével a karaktereken egy ciklussal. Amikor aposztrofhoz érnék, egy változót 1-be billentenék, ez jelzi, hogy aposztrofon belül vagyunk, itt minden adat, sem a szóköz, sem a vessző nem mező szeparátor. A mező alatt most az adatbázis szempontjából értem, hiszen az awk szempontjából egyetlen karakter egy mező. Közben az aktuális karaktert hozzáfűzném az eddig gűjtött karakterekhez. Amikor jön egy aposztrof, visszabillenteném a flag-et 0-ba, jelezvén, hogy most már a vessző, szóköz mező szeparátor jelentéssel bír. Az összegyűjtött stringet kiírnám egy tömb első elemébe, indexet növelném, stringet üresre inicializálnám. A következő aposztrofig mindent eldobnék. Jön megint az aposztrof, kezdődik minden elölről.

Sor végén nem tudom, mi a célod, de van egy tömböd, benne az elemekkel. Tudsz benne keresni, stdout-ra printelni, ami megvan, s ezt mind awk-n belül.

Szerk.: Megcsináltam, ilyen lett:

#!/usr/bin/awk -f

BEGIN {
    if (ARGC!=3) {
        exit(1);
    }
    idx=ARGV[1];
    delete ARGV[1];
    FS="";
}

{
    s="";
    quot=0;
    num=0;
    j=0;
    for (i=1; i<=NF; i++) {
        if (!quot && !num && $i ~ /[[:digit:]]/) {
            num=1;
        }
        if (num && $i !~ /[[:digit:]]/) {
            t[j++]=s;
            s="";
            num=0;
            continue;
        }
        if ($i=="'") {
            if (quot) {
                t[j++]=s;
                s="";
            }
            quot=1-quot;
            continue;
        }
        if (quot || num) {
                s=s $i;
        }
    }
    if (num) {
        t[j++]=s;
    }
    if (idx<0 || idx>=j) {
        printf("Invalid index: %d, [%d..%d]\n", idx, 0, j-1) >"/dev/stderr";
        exit(1);
    }
    printf("%s\n", t[idx]);
}

Így kell hívni:

./bzs 2 bzs.dat

Az első paraméter az, hogy hanyadik oszlopot szedje ki, ez nulla bázisú. Tegyük hozzá, nem tudom, mi szerint kellene szűrni, szóval a script annak megfelelően módosítandó.

Már az aposztrofokkal nem körülvett számokat is kezeli.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Én nagyon szeretem az awk-t. Lényegesen gyorsabb, mint a bash, igaz, más is a dolga. Szöveges bűvészkedéshez nagyon alkalmas. Lusta voltam kommentelni, ha valami nem világos, kérdezz. A ciklus vége után azért vizsgálom a num-ot, mert ha az utolsó elem egy szám, amely nincs idézőjelben, akkor az s nevű bufferben maradna enélkül a vizsgálat nélkül.

Szerk.: A num változóm akkor 1, ha számjegyeken haladunk végig, a quot változóm pedig az aposztrofok között 1, tehát amikor biztosan nem szeparáljuk a cellákat.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Különben ez így nézne ki, de nem korrekt, mert az aposztrof a szeparátor része, az első elemben elöl, az utolsóban hátul ott marad az aposztrof:

awk "BEGIN {FS=\"', '\";} {print(\$3);}" <<<"'regwe', 'khotkml', 'jgoij 43iojt'"

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

bzs, figyelj, mutatom a megoldást:

awk -v idx=3 'BEGIN {FS=", ";} {print(substr($idx, 2, length($idx)-2));}' <<<"'regwe', 'khotkml', 'jgoij 43iojt'"

:)

Szerk.: természetesen az a kritérium, hogy az adatfile-ban az aposztrofok között - tehát cellán belül - nem lehet vessző és szóköz egymás után.

Szerk.2.: A picit fentebb írt önálló awk script teljesebb.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Köszönöm. :) Ha csak azért nem a nagyobbacska scriptemre reagáltál, hogy tudjam editálni, akkor köszi, de amúgy az a jó megoldás. Az kezeli azt az esetet, ha az aposztrofok belsejében van vessző+szóköz, valamint az aposztrof nélküli számokat is. Amire most válaszoltál, az csak akkor működik, ha az aposztrofok között, tehát a cellában nincs vessző+szóköz.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Az én probálkozásom végül így fest:

nff@sedre ~ $ echo "'el,'ső', 'máso, dik', 'har' 'madik'" > string1
nff@sedre ~ $ cat string1
'el,'ső', 'máso, dik', 'har' 'madik'
nff@sedre ~ $ sed "s/^'//; s/'$//; s/', '/ELVALASZTO/g" string1 | awk -F "ELVALASZTO" '{print $3}'
har' 'madik
nff@sedre ~ $ sed "s/^'//; s/'$//; s/', '/ELVALASZTO/g" string1 | awk -F "ELVALASZTO" '{print $2}'
máso, dik
nff@sedre ~ $ sed "s/^'//; s/'$//; s/', '/ELVALASZTO/g" string1 | awk -F "ELVALASZTO" '{print $1}'
el,'ső
nff@sedre ~ $ echo "'el, 'ső', 'máso, dik', 'har' 'madik'" > string1
nff@sedre ~ $ sed "s/^'//; s/'$//; s/', '/ELVALASZTO/g" string1 | awk -F "ELVALASZTO" '{print $1}'
el, 'ső
nff@sedre ~ $ echo "'el,'ső', 'máso, dik', 'har','madik'" > string1
nff@sedre ~ $ sed "s/^'//; s/'$//; s/', '/ELVALASZTO/g" string1 | awk -F "ELVALASZTO" '{print $3}'
har','madik

Mit csinálsz akkor, ha szerepel az adatban az ELVALASZTO szöveg?

Szerk.: A $ jel elé kell egy backslash, mert az idézőjel miatt csak részleges elzárás van a shelltől.

sed "s/^'//; s/'\$//; s/', '/ELVALASZTO/g" string1

Szerk.: A specifikáció pontatlan volt, lehetnek numerikus értékek a cellákban, amelyeket nem vesz körül aposztrof, s így már nem lesz jó ez a mező szeparátor az awk-nak. Mondjuk ez nem a te hibád, csak a minta adatból derült ki.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

"A $ jel elé kell egy backslash, mert az idézőjel miatt csak részleges elzárás van a shelltől."
S, tényleg!
Elképesztő nagy gyakorlatod lehet, hogy rögtön kiszúrod hibát, pedig itt a kimenetből nem is látszik, hogy hibás.
Miért is?

Félre ne érts, tudom, hogy igazad van, mert a "-es elzárásnál a $ kivétel, hogy változókat is lehessen értelmeztetni, de valamiért hibásan is működik nálam ez.
Még úgy is, ha string1-be beszúrok '$ -t:
nff@sedre ~ $ vi string1
nff@sedre ~ $ cat string1
'el,'ső', 'máso, dik', 'ha'$r','madik'
nff@sedre ~ $ sed "s/^'//; s/'$//" string1
el,'ső', 'máso, dik', 'ha'$r','madik

Tényleg üdítő ennyi jó ötlet egy rakáson! ;)

Angol, magyar, meg még néhány európai nyelv karaktereit tartalmazó adatokat

| PIPE

mezőelválasztással kell ábrázolni.

Az egyes mezőket nem quoted kell kiírni.

A mezőelválasztó nem mezőelválasztó+SPACE, hanem csak mezőelválasztó. Ez lehetővé teszi az első és utolsó mező azonos kezelését. Ilyen formátum exportálása adatbázisból, excelből lehetséges!

Ha speciális karaktereket tartalmazó mezőnk is van, azt sajnos külön kell kezelni. Például az ssh kulcs azaz bináris adat ábrázolásánál népszerű a base64 kódolás. Persze ilyen szöveges adatcserénél ritkábban fordul elő.

Az ilyen szöveges állományok feldolgozásánál az awk program így kezdődhet:

awk -F\| \
'BEGIN {
OFS=FS
...

Legyek egy kicsit ontopic is: explicit aposztróf írása awk-n belül:

"\47"

Azaz a helyes mezőelválasztó:

"\47 "

Persze utána minden mezőt a

sub(/^\47/,"",{MEZŐ})

és a

sub(/\47$/,"",{UTOLSÓ_MEZŐ})

függvénnyekkel kell "gyógykezelni"! (No, ezért írtam: Ez lehetővé teszi az első és utolsó mező azonos kezelését.)
Ezek csak akkor működnek, ha nincs mezőelválasztó a mezőkön belül. Csak azért mondom, mert láttam már ilyet hibás konverzió, vagy hibás terminál beállítás esetén.

ha nem akarsz szenvedni a escapelessel akkor erdemesebb az awk scriptet kulon filebe tenni. majd awk -f awkscript

A leírásból nekem nem világos, hogy mi a konkrét feladat. Ha csak annyi, hogy a lista elemei legyenek egyenként hozzáférhetők, akkor az én javaslatom a következő:


awk -v FS="'" '{for(i=1;i<=NF;i++) print $i}' | grep -v "^,"

Ez annyit csinál, hogy az adatbázis egy sorának minden elemét új sorba teszi. A sorokon végig lehet menni ciklussal, az elemekkel meg már azt csinálsz, amit akarsz.

---
Science for fun...