Ubuntu Suomen keskustelualueet
Ubuntun käyttö => Ohjelmointi, palvelimet ja muu edistyneempi käyttö => Aiheen aloitti: JA5U - 27.04.16 - klo:15.42
-
Moi
Nyt olis 1000 tekstitiedostoa, jotka pitäisi saada tietokantaan.
Tekstitiedostot ovat jokseenkin rakenteisessa muodossa:
-tunniste ja data samalla rivillä esim: kuvaus;lorem ipsum dolor sit amet
-osassa erotin on = ja osassa pelkkä \s
-pääsääntöisesti data on tekstiä ja numeroita ja osin pvm jne (ohjelmoinin/konekielisen käsittelyn näkökulmsta vahvasti tyypitettyjä?)
-rivien määrä per tiedosto vaihtelee
-kaikilla on kuitenkin yhteiset tunnisteet
Kerkesin jos melko paljon muokkaamaan näitä eli
-kaikilla on sama erotin ;,
-tyhjät rivit on poistettu,
-osa datasta on rakenteistettu omille riveille jne.
Voisin tietysti jatkon kannalta yhdistää käytetyt komennot yhdeksi skriptiksi, mutta kiinnoistaisi tietää, jos tällaiseen tarkoitukseen on olemassa jotain työkaluja?
Kenties ihan graafisellä käyttöliittymällä varustettuja...
Omilla avainsanoilla en löytänyt kovinkaan osuvia.
-
Skripteillähän tuollaiset muunnokset tehdään. Esikäsittelyn voi usein tehdä vaikka shell-skriptillä, joka kutsuu grep, sed tai awk -ohjelmia tarpeen mukaan. Tai sitten suoraan Pythonilla tai jollain muulla vastaavalla nopeaan kehitykseen soveltuvalla kielellä, jossa on tarvittavat välineet ja tietokantarajapinnat.
Graafista käyttöliittymää en itse ole tällaisissa hommissa kaivannut. Ehkä sitten, jos muunnos on automatisoitavissa vain osittain ja vaatisi tiedostokohtaista säätämistä. (Hankalia ovat esim. raporttidokkarit, joista pitäisi poimia vapaasta tekstistä asioita.)
-
Em. muutokset tein tosiaan sedillä ja eikä ollut edes hirvittävän vmäistä.
Tarkemmin ottaen find . -type f -exec sed -i 's/pois/tilalle/g' {} \;
^M oli uusi juttu, mutta senkin sai korvattua, kun käytti \r...
Vissiin otan pisimmästä tiedostosta eli eniten rivejä sisältävästä tarvittavat sarakenimet kantaan varten.
Sen jälkeen voi loopata tiedostot läpi ja muodostaa niistä insert lausekkeet...kai...
-
Milläs saisin nyt poistettu useamman samanlaisen mjonon samalta riviltä?
Esim.
Teksti;01.01.2001Teksti;01.01.2001Teksti;01.01.2001Teksti;01.01.2001Teksti;01.01.2001
Siten, että ensimmäinen kuitenkin säilyy...
-
Jotain tällaista voi kokeilla:
sed -r 's/^(.+)(\1)+$/\1/' input.txt
Vaatii, että merkkijono toistuu täsmälleen samanlaisena, eikä rivillä ole mitään muuta. Jos siellä on yksikin poikkeava merkki, riville ei tehdä mitään. Lisäksi esimerkiksi rivi aaaa muuttuu a:ksi.
-
Jotain tällaista voi kokeilla:
sed -r 's/^(.+)(\1)+$/\1/' input.txt
Vaatii, että merkkijono toistuu täsmälleen samanlaisena, eikä rivillä ole mitään muuta. Jos siellä on yksikin poikkeava merkki, riville ei tehdä mitään. Lisäksi esimerkiksi rivi aaaa muuttuu a:ksi.
Täytyypä illalla kokeilla.
Eilen tein omia kokeliuja, mutta tuloksetta. Tuota edelläkin käytettyä '\1' kokeilin korvausarvona, mutta se aiheutti jonkin herjan.
Tuohan on käsittäkseni viittaus 'capturing groupin' eli sillä saadaan se 1. osuma talteen?
Tämäkin syntaksi on hieman erilainen kuin muissa yhteyksissä.
En saanut myöskään noita sulkumerkkejä toimimaan () - kokeilin kenoviivan kanssa ja ilman.
En tiedä, että onko joku versio juttu, mutta itselläni on 15.04 asennettuna
-
Eilen tein omia kokeliuja, mutta tuloksetta. Tuota edelläkin käytettyä '\1' kokeilin korvausarvona, mutta se aiheutti jonkin herjan.
Varmaan ne sulut olivat jotenkin pielessä. Sedin perustilassa ryhmittelysulut pitää tosiaan suojata kenoviivalla.
Lisäksi tässä on kätevää käyttää \{1,\}:n sijaan +:aa, jota sed tukee vain -r -parametrin kautta valittavassa laajennetussa tilassa. Tällöin myöskään sulkuja ei tarvitse suojata. Perustilassa lauseke pitäisi kirjoittaa näin:
sed 's/^\(.\{1,\}\)\(\1\)\{1,\}$/\1/' input.txt
Lisäys:
Huomasin säännöllisten lausekkeiden ahneesta sovituksesta (greedy matching) johtuvan ongelman. Jos merkkijono toistuu parillisen määrän kertoja, tämä lauseke tunnistaa sen koostuvan vain kahdesta osasta, koska peräkkäin asetettuna ne muodostavat alkuperäisen merkkijonon.
5 toistoa:
$ echo "aaaaa" | sed -r 's/^(.+)(\1)+$/\1/'
a
6 toistoa:
$ echo "aaaaaa" | sed -r 's/^(.+)(\1)+$/\1/'
aaa
Tätä voi olla vaikea ratkaista sedillä toistamatta operaatiota useita kertoja: esimerkiksi ajamalla komentoa silmukassa, kunnes tulos ei enää muutu syötteeseen verrattuna.
Perlillä voi käyttää non-greedy-sovitusta (http://docstore.mik.ua/orelly/perl/cookbook/ch06_16.htm): (.+?)
$echo "aaaaaa" | perl -pe 's/^(.+?)(\1)+$/\1/'
a
-
Herjaa pukkaa:
# find . -type f -name "*.txt" -exec sed -ir 's/^([a-z]+\s+[a-z]+;)(\d{2}).(\d{2}).(\d{4}) (\d{2}):(\d{2}):(\d{2})(\1)+$/\1/ig' {} \;
sed: -e expression #1, char 88: Invalid back reference
Tässä siis täsmää "Sana sana;dd.mm.yyyy hh:mm:ss".
-
Kun ei se sujunut, niin kylmästi katkaisin viimeisen rivin X merkkimäärän kohdalta.
Tämä siis nappaa viimeisen rivin 36 merkkiä ja korvaa koko rivin niillä merkeillä.
find . -type f -name "*.txt" -exec sed -i -r '$ s/(.){36}/\1/ig' {} \;
-
Herjaa pukkaa:
# find . -type f -name "*.txt" -exec sed -ir 's/^([a-z]+\s+[a-z]+;)(\d{2}).(\d{2}).(\d{4}) (\d{2}):(\d{2}):(\d{2})(\1)+$/\1/ig' {} \;
sed: -e expression #1, char 88: Invalid back reference
Tässä siis täsmää "Sana sana;dd.mm.yyyy hh:mm:ss".
Virhe johtui tästä:
sed --help
-i[SUFFIX], --in-place[=SUFFIX]
edit files in place (makes backup if SUFFIX supplied)
Eli sed tulkitsee parametrin "-ir" parametriksi "-i", jolla on määreenä väliaikaistiedoston päätteenä käytettävä merkkijono "r". Parametri -r ei siis mene perille ja säännöllisen lausekkeen syntaksi ei kelpaa. Komento on kirjoitettava, kuten myöhemmässä skriptissäsi teit: "sed -i -r"
Ongelma 2
Sed ei tulkitse \d:tä numeroksi. Käytä sen sijaan syntaksia [0-9] tai merkkiluokkaa [[:digit:]]
Myös \s on vähän epävarma, jos skriptin pitäisi olla siirrettävissä muihin järjestelmiin. Vain GNU sed tukee sitä. [[:space:]] on laajemmin tuettu.
Perl tukee \d:tä ja \s:ää.
Ongelma 3
Skripti ei ratkaise esitettyä tehtävää. Olkoon toistuva merkkijono esimerkiksi "yks kaks;28.04.2016 10:11:22":
echo "yks kaks;28.04.2016 10:11:22yks kaks;28.04.2016 10:11:22" | sed -r 's/^([a-z]+[[:space:]]+[a-z]+;)([0-9]{2}).([0-9]{2}).([0-9]{4}) ([0-9]{2}):([0-9]{2}):([0-9]{2})(\1)+$/\1/ig'
Tulos: yks kaks;28.04.2016 10:11:22yks kaks;28.04.2016 10:11:22
Ulos saatiin syötetty merkkijono, eli lauseke ei tunnistanut toistoa. Sen sijaan skripti osaa tehdä tällaisen muunnoksen:
"yks kaks;28.04.2016 10:11:22yks kaks;" --> "yks kaks;"
Tai:
"yks kaks;28.04.2016 10:11:22yks kaks;yks kaks;yks kaks;" --> "yks kaks;"
Alkuperäinen ongelma ratkeaa tällaisella lausekkeella, jos siis halutaan speksata toistuvan merkkijonon hyväksytty sisältö tarkasti:
sed -i -r 's/^([a-z]+[[:space:]]+[a-z]+;[0-9]{2}.[0-9]{2}.[0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2})(\1)+$/\1/' input.txt
Tässä on poistettu ylimääräiset sulut numerokenttien ympäriltä ja siirretty päivämäärä ja aika toistuvan ryhmän sisään.
PS. Kuten mitä tahansa ei-triviaalia koodia, säännöllisiä lausekkeita kannattaa testata aluksi yksittäisillä syötteillä sen sijaan, että ajaa koko datasetin skriptin läpi. Lausekkeita joutuu aina viilaamaan jonkin verran ja testailemaan rajatapauksia. Yleensä on helpointa vain putkittaa testattava syöte skriptille komentorivillä. Lähdetiedostojen muuttamista (sed -i) on syytä välttää kehityksen aikana, ja ehkä muutenkin.
-
Ongelma 2
Sed ei tulkitse \d:tä numeroksi. Käytä sen sijaan syntaksia [0-9] tai merkkiluokkaa [[:digit:]]
Myös \s on vähän epävarma, jos skriptin pitäisi olla siirrettävissä muihin järjestelmiin. Vain GNU sed tukee sitä. [[:space:]] on laajemmin tuettu.
Perl tukee \d:tä ja \s:ää.
Hmh...
Ongelma 3
Alkuperäinen ongelma ratkeaa tällaisella lausekkeella, jos siis halutaan speksata toistuvan merkkijonon hyväksytty sisältö tarkasti:
sed -i -r 's/^([a-z]+[[:space:]]+[a-z]+;[0-9]{2}.[0-9]{2}.[0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2})(\1)+$/\1/' input.txt
Tässä on poistettu ylimääräiset sulut numerokenttien ympäriltä ja siirretty päivämäärä ja aika toistuvan ryhmän sisään.
Näinhän se tosiaan toimii. Unohdin tarkentaa, että nuo mahdolliset toistot sisältävät eri ajan eli eivät täsmää \1.
Kuitenkin tuo viimeisen rivin muokkaus oli toimiva temppu tässä tapauksessa, kun ne olivat aina viimeisellä rivillä.
Olisi pitänyt heti alussa muokata kaikista tiedostoista tuo "Sana sana" -> "sana". Ei se hirveästi selkeytä tarvittavaa lauseketta, mutta kuitenkin...
http://www-rohan.sdsu.edu/doc/sed.html
-
Nyt tarttis saada poistettu tietyiltä riveiltä ;-merkin jälkeen kaikki muut merkit paitsi numerot ja väliviiva?
esim "rivintunniste; noin. 10-15" -> "rivintunniste;10-15"
En tiedä, että onko sed tähän paras vai joku muu?
Sitten, kun tiedostot on kunnossa, niin ne pitäisi saada myös käännetty riveiltä sarakkeisiin.
Tuommosta https://sourceforge.net/projects/transpose/ ehdoteltiin jossain, mutta en saanut sitä toimimaan.
EDIT: Nyt sain toimimaan, kun löysin yhdestä ketjusta ohjeet, että kuinka sitä käytetään: transpose -t --fsep ";" input.txt > output.txt
Tosin, tulokset olivat sinne päin eli tiedostosta muodostui kolmirivinen kahden sijaan, jossa on muutamia otsakkeita sikinsokin.
Toinen minkä löysin gnu datamash, mutta se taas kaatui datamash: transpose input error: line 4 has 2 fields (previous lines had 3);
Johtuuko sitten ääkkösistä? Hieman menee iteroiden, pitääkin käyttää lisää valitsimia --no-strict -filler 0
Joka tosin kaatuu:datamash: malloc.c:3096: sYSMALLOc: Assertion `(old_top == (((mbinptr) (((char *) &((av)->bins[((1) - 1) * 2])) - __builtin_offsetof (struct malloc_chunk, fd)))) && old_size == 0) || ((unsigned long) (old_size) >= (unsigned long)((((__builtin_offsetof (struct malloc_chunk, fd_nextsize))+((2 * (sizeof(size_t))) - 1)) & ~((2 * (sizeof(size_t))) - 1))) && ((old_top)->size & 0x1) && ((unsigned long)old_end & pagemask) == 0)' failed.
Aborted (core dumped)
-
Sitten, kun tiedostot on kunnossa, niin ne pitäisi saada myös käännetty riveiltä sarakkeisiin.
Tuommosta https://sourceforge.net/projects/transpose/ ehdoteltiin jossain, mutta en saanut sitä toimimaan.
EDIT: Nyt sain toimimaan, kun löysin yhdestä ketjusta ohjeet, että kuinka sitä käytetään: transpose -t --fsep ";" input.txt > output.txt
Tosin, tulokset olivat sinne päin eli tiedostosta muodostui kolmirivinen kahden sijaan, jossa on muutamia otsakkeita sikinsokin.
Esiintyykö erotin ";" jokaisella rivillä kerran ja vain kerran? Muutenhan sarakkeita on eri määrä eri riveillä ja transpose varmaankin sekoaa siihen.
Kyhäsin huvikseni shell-skriptin samaan hommaan, vaikka helpommin kyllä onnistuisi jollain kehittyneemmällä kielellä.
#!/bin/sh
if [ $# -lt 1 ]; then
echo "Transpose delimited data"
echo ""
echo "usage: $0 input.txt > output.txt"
echo ""
exit
fi
input="$1"
delimiter=";"
substitute=","
printrow() {
outdelim=""
echo "$1" | while IFS= read -r val; do
echo -n "$outdelim$val"
outdelim="$delimiter"
done
}
# Regular expression to match two columns separated by $delimiter
matchcolumns="^\([^$delimiter]*\)$delimiter\(.*\)$"
# Pick first column
header=$(sed -e "s/$matchcolumns/\1/" "$input")
# Pick rest of the columns. They are all treated as one combined column.
data0=$(sed -e "s/$matchcolumns/\2/" "$input")
# Substitute leftover delimiters with $substitute to avoid mismatching pairs
data=$(echo "$data0" | tr "$delimiter" "$substitute")
printrow "$header"
echo ""
printrow "$data"
echo ""
Nyt tarttis saada poistettu tietyiltä riveiltä ;-merkin jälkeen kaikki muut merkit paitsi numerot ja väliviiva?
esim "rivintunniste; noin. 10-15" -> "rivintunniste;10-15"
En tiedä, että onko sed tähän paras vai joku muu?
Taitaa mennä aika vaikeaksi pelkällä sedillä. Awk:lla onnistunee helpommin. Tässä shell+sed -ratkaisu sovellettuna yllä esitetystä:
inputfile="$1"
delimiter=";"
# Remove stuff from the second column. This expression keeps numbers 0-9 and the dash character and removes everything else.
removethis="[^0-9-]"
matchcolumns="^\([^$delimiter]*\)$delimiter\(.*\)$"
cat "$inputfile" | while IFS= read -r val; do
header=$(echo "$val" | sed -e "s/$matchcolumns/\1/")
data=$(echo "$val" | sed -e "s/$matchcolumns/\2/; s/$removethis//g")
echo "$header$delimiter$data"
done
-
Tästähän alkaa paisua jo aika hc ketju.
Pitääpä laittaa illalla testaten noita.
Esiintyykö erotin ";" jokaisella rivillä kerran ja vain kerran? Muutenhan sarakkeita on eri määrä eri riveillä ja transpose varmaankin sekoaa siihen.
Kyllä, valitsin tuon merkin erottimeksi siksi, että sitä ei esiintynyt tekstissä muutoin.
Eilen kävi ilmi, että ilmeisesti tuo datamash sekosi siitä syystä, että kyseisellä rivillä tunniste-arvoparilta puuttui arvo: "tunniste;".
--no-strict ilmeisesti estää sen kaatumisen, mutta sitä käyttämällä tuli taas tuo aiemmin ilmoitettu virhe, joka laukaisi myös Ubuntun oman virheilmoituksen...
Kokeilin myös vaihtaa rajoitinmerkiksi :, mutta "tulokset" olivat siitä huolimatta vastaavanlaisia.
Tuolla ;-merkillä se itseasiassa katkaisi koko komennon, jos sitä käytti ohjeiden tapaan -t; ilman hipsuja.
-
Menee kyllä vähä ylihilseen nuo aiemmat skriptit.
Johtuu siitä, etten tunne tuota syntaksia, erikoismerkkejä taikka muuttujien "käsittelyä".
Loogisesti ajateltuna tässä on melko yksinkertaisesti ongelmasta kyse:
luetaan rivi,
jaetaan se kahteen osaan ;-merkin perustella,
1. osa menee 1. rivin jatkoksi ja
2. osa 2. rivin jatkoksi.
Jotenkin tätä pähkäilin ja rupesin miettimään sedittämistä siten, että suoritetaan useampi komento sed -e '' -e ''
En kuitenkaan ymmärrä, että miten tuon ensimmäisen sed komennon tulokset saisi siirretty seuraavalle, joka taas muokkaisi tiettyä tiedostoa edellä kuvatun mukaisesti.
Tai ts. miten \1 ja \2 saisi siirrettyä eteenpäin sedille/bashille käsiteltäväksi, joka sitten suorittaisi \1 lisäyksen riville 1 ja \2 lisäyksen riville 2 tiettyyn tiedostoon?
Eli
halkaise;minut
halkaiset;minutkin
...
tulee
halkaise,halkaiset
minut,minutkin
...
Pseudo
load input
while read line
split line to array
open output
write ','+array[1] to output line 1
write ','+array[2] to output line 2
-
Menee kyllä vähä ylihilseen nuo aiemmat skriptit.
Johtuu siitä, etten tunne tuota syntaksia, erikoismerkkejä taikka muuttujien "käsittelyä".
Oliko skriptin käytössä tai toiminnassa jokin ongelma? Ei pitäisi olla tarvetta muokata sitä mitenkään, jos tarkoituksena on erottaa ;-merkillä erotetut kaksi saraketta ja kirjoittaa arvot ;-merkillä erotettuina kahdelle riville. Tekee saman kuin transpose, mutta pitäisi toimia lähes kaikilla kuviteltavissa olevilla syötteillä.
Loogisesti ajateltuna tässä on melko yksinkertaisesti ongelmasta kyse:
luetaan rivi,
jaetaan se kahteen osaan ;-merkin perustella,
1. osa menee 1. rivin jatkoksi ja
2. osa 2. rivin jatkoksi.
Skriptini käyttää osien erotteluun säännöllistä lauseketta "^\([^;]*\);\(.*\)$"
Ensimmäisen sarakkeen kaikki arvot saadaan siis rivinvaihdoin eroteltuna komennolla:
sed -e "s/^\([^;]*\);\(.*\)$/\1/" tiedosto.txt
Toisen sarakkeen arvot saadaan vastaavasti komennolla:
sed -e "s/^\([^;]*\);\(.*\)$/\2/" tiedosto.txt
Nämä tallennetaan muuttujiin (ensimmäinen sarake muuttujaan header ja toinen sarake muuttujaan data0. Skriptin printrow-funktio muuntaa rivinvaihdot ;-välimerkeiksi ja tulostaa rivit stdoutiin, jonka voit ohjata haluamaasi tiedostoon.
-
Eipä näissä koodeissa liene vikaa, pikemminkin lukijassa.
Vkloppuna ajelin näitä ja CSVhen tuli kolme riviä.
En oikein tiedä, että mistä se johtuu, kun sama tapahtui muillakin ratkaisulla.
Jotenkin tuntuu, että tiedostoissa olisi jotain mätää, mutta ei ainakaan näytä olevan.
-
Eipä näissä koodeissa liene vikaa, pikemminkin lukijassa.
Vkloppuna ajelin näitä ja CSVhen tuli kolme riviä.
En oikein tiedä, että mistä se johtuu, kun sama tapahtui muillakin ratkaisulla.
Jotenkin tuntuu, että tiedostoissa olisi jotain mätää, mutta ei ainakaan näytä olevan.
Sitten tarvittaisiin esimerkki, jossa näin tapahtuu. En oikein näe, miten se olisi mahdollista, koska skripti tulostaa vain kaksi riviä, eikä riveille listattavissa sarakkeissa voi olla rivinvaihtoja (LF).
Ainut, mikä tulee mieleen on ylimääräinen CR-merkki jossain välissä, ja tekstieditorisi tulkitsee sen rivinvaihdoksi..?
-
Sitten tarvittaisiin esimerkki, jossa näin tapahtuu. En oikein näe, miten se olisi mahdollista, koska skripti tulostaa vain kaksi riviä, eikä riveille listattavissa sarakkeissa voi olla rivinvaihtoja (LF).
Ainut, mikä tulee mieleen on ylimääräinen CR-merkki jossain välissä, ja tekstieditorisi tulkitsee sen rivinvaihdoksi..?
Eletäänkö eri aikavyöhykkeillä? :) Vai vaan eri vrkrytmissä...
Testaamalla testasin nyt ja totesin, että ongelmat johtuvat rivistä, jolla ei ole tunnisteelle arvoa.
Eli jostain syystä kaikki ratkaisut sekoavat, jos rivillä on pelkkä tunniste eikä arvoa:
column_name;
-
En onnistu toistamaan. Edes puuttuva sarakkeen nimi ei haittaa:
input.txt:
column_1;value1
column_2;
column_3;value3
;value4
column_5;value5
Skriptin läpi ajettuna:
./tp.sh input.txt
column_1;column_2;column_3;;column_5
value1;;value3;value4;value5
Laita viestin liitteeksi tekstitiedosto, jossa vika esiintyy.
-
Jopas on taas bittimaailman ihmeitä.
Eilen seditin kaikki ; loppuiset rivit muotoon ;- ja tokihan silloin toimi.
Ja valitettavasti tiedosto sisältää tietoa, jota ei voi luovuttaa eteenpäin.
Eikä oikein pätkäkään auta tässä tapauksessa.
Muutoin olisin heittänyt sellaisen heti aloituksessa.
-
Jopas on taas bittimaailman ihmeitä.
Eilen seditin kaikki ; loppuiset rivit muotoon ;- ja tokihan silloin toimi.
En kyllä keksi, miten arvon puuttuminen voisi aiheuttaa ongelmia tuon skriptin kanssa. Siellä täytyy olla jotain muutakin erikoista rivinvaihtoihin liittyen.
Ja valitettavasti tiedosto sisältää tietoa, jota ei voi luovuttaa eteenpäin.
Eikä oikein pätkäkään auta tässä tapauksessa.
Muutoin olisin heittänyt sellaisen heti aloituksessa.
Yksi tapa löytää ongelmia aiheuttava kohta on leikata tiedostoa osiin puolittaen aina sen osan, jossa ongelma esiintyy. Lopulta pitäisi olla käsissä vain pieni pätkä, josta syyn voi löytää esimerkiksi heksaeditorin avulla. Silloin on myös mahdollista korvata arkaluontoiset merkkijonot geneerisillä nimillä esimerkkiä varten.
-
Ei tää varmaan hirveästi auta, mutta CSV on siis seuraavanlainen:
rivi1_tunniste;rivi2_tunniste;rivi3_tunniste;...riviN_tunniste;
rivi1_arvo;rivi2_arvo;rivi3_arvo;rivi4_arvo
rivi4_arvo;rivi5_arvo...riviN_arvo;
Jaaaa, vaan nyt tuolla cat -v tarkasteltuna siellä onkin edelleen ^M merkkejä. Eli tuo ^M varmaan näyttäytyy tuollaisena "virheellisenä" CSVnä?
EDIT: yllättäen en enää muista, että kuinka sain osan niistä pois. SEDillä ei vaikuta luonnistuvan.
-
Jaaaa, vaan nyt tuolla cat -v tarkasteltuna siellä onkin edelleen ^M merkkejä. Eli tuo ^M varmaan näyttäytyy tuollaisena "virheellisenä" CSVnä?
^M = \r = CR eli Carriage Return. DOS- ja Windows-ohjelmat käyttävät CR+LF:ää (\r\n) rivinvaihtona, kun Unixissa käytetään pelkkää LF:ää.
tr on paras työkalu yksittäisten tai peräkkäin toistuvien merkkien muuntamiseen ja poistoon:
tr -d '\r' < input.txt > output.txt
Jos syötteessä on CR-merkkejä, rivien määrä riippuu tulkinnasta. Jotkut ohjelmat tulkitsevat ne rivinvaihdoiksi (esim. gedit tekee niin) ja jotkut näyttävät ^M-merkinnän tai jotain muuta.
-
Mitenkäs tämä tr komento yhdistetään find jatkeeksi?
find . -name "*.txt" -type f -exec tr -d '\r' < jotain > jotain #mutta miten
-
Mitenkäs tämä tr komento yhdistetään find jatkeeksi?
find . -name "*.txt" -type f -exec tr -d '\r' < jotain > jotain #mutta miten
Esimerkiksi näin, jos haluat tehdä tiedostoille inplace-käsittelyn, eli kohdetiedosto on sama kuin lähde:
find . -name "*.txt" -type f -exec sh -c 'cp {} "{}.tmp"; tr -d "\r" < "{}.tmp" > {}; rm "{}.tmp"' \;
Tai sedillä (mutta vain GNU-versioissa, ja muutenkin pienellä varauksella, koska eri sed-versiot saattavat käsitellä CR+LF-rivinvaihtoja eri tavoin):
find . -name "*.txt" -type f -exec sed -i -e 's/\r//g' {} \;
-
Mitenkäs tämä tr komento yhdistetään find jatkeeksi?
find . -name "*.txt" -type f -exec tr -d '\r' < jotain > jotain #mutta miten
Esimerkiksi näin, jos haluat tehdä tiedostoille inplace-käsittelyn, eli kohdetiedosto on sama kuin lähde:
find . -name "*.txt" -type f -exec sh -c 'cp {} "{}.tmp"; tr -d "\r" < "{}.tmp" > {}; rm "{}.tmp"' \;
Tai sedillä (mutta vain GNU-versioissa, ja muutenkin pienellä varauksella, koska eri sed-versiot saattavat käsitellä CR+LF-rivinvaihtoja eri tavoin):
find . -name "*.txt" -type f -exec sed -i -e 's/\r//g' {} \;
Tämä oli muuten hyvä, mutta siinä kävi kuten epäilinkin eli nyt rivinvaihtoa tai välimerkkiä ei ole. Ilmeisesti tämä olisi ollut sitten sopivampi:
find . -name "*.txt" -type f -exec sed -i -e 's/\r/\n/g' {} \;
-
Tämä oli muuten hyvä, mutta siinä kävi kuten epäilinkin eli nyt rivinvaihtoa tai välimerkkiä ei ole. Ilmeisesti tämä olisi ollut sitten sopivampi:
find . -name "*.txt" -type f -exec sed -i -e 's/\r/\n/g' {} \;
Olen yrittänyt käsitellä Nordean verkkopankista haettua tapahtumaluetteloa noin, mutta ei toiminut. Tarkoitan kohtaa "sed 's/\r/\n/g'".
Ensinnäkin, se sed-versio joka tulee Ubuntun mukana ei tunnusta \r-merkkiä, kuten nm oikein varoitti. Toiseksi, sedillä ei saa kiinni rivinvaihtoa, koska sed käsittelee inputtia rivi riviltä niin että rivinvaihto ei ole mukana. Jos rivinvaihdon haluaa korvata jollain, sedille pitää erikseen kertoa että input halutaan lukea yhtenä rivinä (http://stackoverflow.com/questions/1251999/how-can-i-replace-a-newline-n-using-sed), mikä on hirveän ruman näköistä.
Perlillä sen voi tehdä siististi:
perl -pe 's/\r\n//g' < input.txt > output.txt
mutta perl on liian järkäle näin pieneen juttuun.
Eipä yksittäisen tiedoston kanssa huomaa että cpua kuluu, mutta jos tiedostoja on paljon, niin sitten kannattaa nyplätä ja virkata sedin kanssa.
EDIT: Uups. Luin väärin kohdan "sed 's/\r/\n/g'" koska olin juuri ollut käsittelemässä tätä Nordean tapahtumatiedoston tapausta. Tuo "sed 's/\r/\n/g'" toimii jos sed tunnistaa \r:n. Mun tapauksessa siis oli tosiaan eri tilanne: "sed 's/\r\n//g'".
-
Tämä oli muuten hyvä, mutta siinä kävi kuten epäilinkin eli nyt rivinvaihtoa tai välimerkkiä ei ole.
Kokeilitko tr:llä? Testaa ensin yksittäistä tiedostoa, jossa ongelma esiintyy. Ilman näytettä en oikein keksi, mikä noissa muunnoksissa voisi aiheuttaa ongelmia.
Voit myös tutkia rivinvaihtoja heksaeditorilla. \r on ASCII-merkki 13 eli heksadesimaalina 0d. LF-rivinvaihto \n on 10 eli 0a
-
Olis pitäny hieman tarkentaa tilannetta.
Tarkoitin siis sitä, että siinä ^M tilalla tulisi olla se rivinvaihto.
Eli nyt sen ^M tilalle ei tullut mitään ja kaksi riviä ovat yhdessä.
Ts. rivi1;asd^Mrivi2;asd -> rivi1;asdrivi2;asd
Toinen kysymys eli miten tuolle datamash komennolle voisi syöttää tuon findin tuloksen?
Nyt se herjaa #find . -name '*.txt' -type f -exec datamash -t';' {} \;
datamash: invalid operation './asd.txt'
Lyhyesti päättelin, että se johtuisi tuosta ./asd.txt eli sen pitäisi olla asd.txt.
Tod.näk. olen kuitenkin väärässä :)
-
Toinen kysymys eli miten tuolle datamash komennolle voisi syöttää tuon findin tuloksen?
Nyt se herjaa #find . -name '*.txt' -type f -exec datamash -t';' {} \;
datamash: invalid operation './asd.txt'
Käyttöohjeen perusteella se lukee syötteen stdinistä ja käsitelty data annetaan ulos stdoutin kautta. Toimii siis kuten tr.
datamash -t';' transpose < input.txt > output.txt
find . -name "*.txt" -type f -exec sh -c 'cp {} "{}.tmp"; datamash -t';' transpose < "{}.tmp" > {}; rm "{}.tmp"' \;
-
No alkaahan tämä jo hiljalleen skulaamaan :)
Kaikkea oppii tai luulee oppivansa jatkuvasti - vielä, kun vaan muistaisi seuraavallakik kerralla, että miten se nyt olikaan.
Lopulta oikasin hieman:
find . -name '*.txt' -type f -exec bash -c 'txt2csv.sh "{}" > "{}".csv' \;
renamella muutin lopuksi txt.csv -> .csv, mutta joku hieno pikkujuttu varmaan mahdollistaisi suoraan csv päätteen.
Nyt pitäisi imasta nämä kantaan...
Toivottavasti kukaan ei tässä vaiheessa totea, että miksi nähdä näin paljon vaivaa, kun asian olisi voinut hoitaa jotenkin toisin ;D
...mutta aikuisten oikeasti, jos on joku virtaviivaisempi keino, niin saa toki kertoa.
-
No eipä tämä nyt suju.
Nuo ^M merkit kummitelevat edelleen ja tekevät mielenkiintoisia omituisuuksia PHP:ssä fgetcsvllä.
Ne pitäisi siis saada pois, mutta miten?
Ei auta:
find . -type f -name "*.txt" -exec vi {} -c ':silent!' -c ':%s/\^M/\r/G' -c ':wq' \;
Ei auta:
find . -type f -name "*.txt" -exec vi {} -c ':silent!' -c ':%s/<Ctrl-V><Ctrl-M>/\r/g' -c ':wq' \;
EDIT:
Merkillinen havainto, kun komennolla cat
tarkastelee tiedostoa, niin ^M sisältävän rivin alkuosa ei tulostu.
Kuitenkin cat -v
komennolla koko rivi tulostuu.
Rivi on siis muotoa: teksti^Mlisaatekstia (cat -v) tai lisaatekstia (cat)
-
Olisi helpompaa neuvoa, jos pystyisit antamaan pätkän ongelmatiedostosta esimerkkinä. Muuten menee täysin arvailuksi, miten nuo rivinvaihdot ovat seonneet.
-
No eipä tämä nyt suju.
Nuo ^M merkit kummitelevat edelleen ja tekevät mielenkiintoisia omituisuuksia PHP:ssä fgetcsvllä.
Ne pitäisi siis saada pois, mutta miten?
.........
Oletkos moista kokeillut?
:~# aptitude search dos2unix
p dos2unix
-
Kyllä, dos2unix on ollut kokeilussa, mutta sama lopputulos. :-\
Eikös tuossa ylhäällä ole esimerkkirivi:
Rivi on siis muotoa: teksti^Mlisaatekstia (cat -v) tai lisaatekstia (cat)
cat -v
riviX;zxczxc
ongelmarivinalku;asd^Mjaloppu;wasd
riviY;xczxcz
cat
riviX;zxczxc
jaloppu;wasd
riviY;xczxcz
-
Eikös tuossa ylhäällä ole esimerkkirivi:
Rivi on siis muotoa: teksti^Mlisaatekstia (cat -v) tai lisaatekstia (cat)
Haluaisin nähdä palan alkuperäisestä tiedostosta ilman mahdollista editorin tai päätteen tekemää muunnosta, jotta voisin tutkia sitä binäärimuodossa heksaeditorilla. Muuten on vaikea arvata, mitä tiedosto oikeasti sisältää.
hexdumpin listaus kelpaa myös:
hexdump -C tiedosto.txt
-
Muokkasin itselleni tällaisen, joka ratkaisi asian, mutta aiheutti tyypillisen "nokka irtoaa, niin pyrstö tarttuu" eli menetin ääkköset:
#make a backup
cp $1 $1.orig
#Tekee jonku muutoksen ä->a tmv.
iconv -t ascii//TRANSLIT $1.orig > $1.temp
#Strip out other unprintable characters -whatevs
strings $1.temp > $1
#Remove temp file.
rm $1.temp
Ja jos käytän tiedoston Sublimen kautta, niin ongelmat ts.merkit katoavat eikä dataa menetetä.
Joten teen siis komennon joka pyöräyttää tiedostot taustalla Sublimen kautta.
Tässä kuitenkin vielä UUSI hexdump ongelmakohdasta:
00000000 65 72 65 68 64 79 73 3b 0a 65 74 75 6e 69 6d 69 |erehdys;.etunimi|
00000010 3b 74 65 72 6f 0a 73 75 6b 75 6e 69 6d 69 3b 61 |;tero.sukunimi;a|
00000020 6e 74 65 72 6f 0a 6c 61 68 69 6f 73 6f 69 74 65 |ntero.lahiosoite|
00000030 3b 6b 6f 74 69 6b 61 74 75 20 38 |;kotikatu 8|
0000003b
-
Jees, ongelma näkyi siinä dumpissa jonka ehdit poistaa, eli tiedostossa oli enimmäkseen LF-rivinvaihdoilla erotettuja rivejä, mutta yksi CR (0x0d) kohdassa, jossa kuuluisi olla rivinvaihto. Jokin on siis pudottanut siitä kohdasta LF:n (0x0a) pois tai korvannut sen CR:llä.
Varmin ratkaisu olisi käyttää ensin dos2unix-komentoa (tai vastaavaa sed-skriptiä) siltä varalta, että vastaan tulee CR+LF-rivinvaihtoja, mutta jatkaa sitten muuntamalla yksinäiset CR-rivinvaihdot LF-rivinvaihdoiksi. Eli jotakuinkin tähän tapaan:
dos2unix -n tiedosto.txt temp.txt
tr '\r' '\n' < temp.txt > korjattutiedosto.txt
Tämä toimii siis olettaen, että CR-merkkejä on vain sellaisissa kohdissa, joissa kuuluu olla rivinvaihto. Jos niitä on myös muualla, olet syvässä suossa.
-
Tämä toimii siis olettaen, että CR-merkkejä on vain sellaisissa kohdissa, joissa kuuluu olla rivinvaihto. Jos niitä on myös muualla, olet syvässä suossa.
Eli kuten tuossa esimerkkirivillä on keskellä riviä ^M, niin olen suossa?
Mutta jos saan tuon sublimen toimimaan, niin en kenties olekaan...
EDIT:
No eihän se Sublimessä pyöräyttäminen muuttanut mitään, kun se tehtiin komentorivin kautta.
Jos avaa käyttöliittymässä, niin silloin kyllä tiedostot korjaantuvat pelkällä 'save' toiminnolla.
HUOH...
-
Tämä toimii siis olettaen, että CR-merkkejä on vain sellaisissa kohdissa, joissa kuuluu olla rivinvaihto. Jos niitä on myös muualla, olet syvässä suossa.
Eli kuten tuossa esimerkkirivillä on keskellä riviä ^M, niin olen suossa?
Kolme mahdollista tapausta:
1. CR-merkkejä on vain keskellä riviä, mahdollisesti satunnaisissa paikoissa. Oikeat rivinvaihdot on merkitty LF:llä tai CR+LF:llä.
Ratkaisu: poistetetaan kaikki CR-merkit. Tähän on esitetty sekä tr- että sed-komennot jo aiemmin.
2. CR-merkkejä on vain sellaisissa paikoissa, joissa kuuluu olla rivinvaihto, mutta LF saattaa puuttua.
Ratkaisu: dos2unix ja yksittäisten CR-merkkien muuntaminen LF-merkeiksi esim. tr-työkalulla, kuten esitin edellisessä viestissäni.
3. Yksittäisiä CR-merkkejä on sekä rivinvaihtojen kohdilla että keskellä riviä.
Mikään triviaali poisto tai muunnos ei pysty päättelemään, merkkaako CR rivinvaihtoa vai onko se turha. Tässä on todellinen ongelma, joka ei ehkä ole ratkaistavissa ilman heuristiikkaa tai koneoppimista. Jos tilanne on tämä, olisi todennäköisesti helpompaa lähteä aivan alusta selvittämään, mistä CR-merkit ovat dataan päätyneet.
Aiempi hexdump-listaus, jonka poistit (ja korvasit siistityllä esimerkillä jossa ei ole ainoatakaan CR-merkkiä), viittasi tapaukseen 2 tai 3. Siinä ei siis ollut CR:ää rivin keskellä vaan rivinvaihdon kohdalla:
etunimi;tero^Msukunimi;antero
Uudessa esimerkissä tuo CR on vaihtunut LF:ksi:
etunimi;tero
sukunimi;antero
Jos siis datassa esiintyy sekä tällaisia tapauksia että yksittäisiä CR-merkkejä keskellä riviä vaikkapa nimen sisällä, olet pulassa.
-
Latasin wxhexeditorin ja siinä virhekohdassa näkyy nuotti.
Taisin ajaa findillä kansioon nuo korjaukset, jonka jälkeen otin uuden hexdumpin, kun huomasin, että aiemmasta puuttui c kahva.
00000000 79 72 69 74 79 73 3b 0a 65 74 75 6e 69 6d 69 3b |yritys;.etunimi;|
00000010 48 65 72 72 61 0d 73 75 6b 75 6e 69 6d 69 3b 4d |Herra.sukunimi;M|
00000020 61 6a 75 72 69 0a 6c 61 68 69 6f 73 6f 69 74 65 |ajuri.lahiosoite|
00000030 3b 4b 6f 74 69 6b 61 74 75 20 34 0a |;Kotikatu 4.|
0000003c
Jos tarkastelen tiedostoa komennolla
od -ctx1 test.txt
niin siinä näkee taas sen, että tuo yksi on \r ja loput \n.
-
Jos tarkastelen tiedostoa komennolla
od -ctx1 test.txt
niin siinä näkee taas sen, että tuo yksi on \r ja loput \n.
Jep, juuri niin. Ja tässä tapauksessa merkin \r (= CR = ^M = 0d) kohdalla kuuluisi olla rivinvaihto, vai mitä? Eli se pitäisi muuntaa \n:ksi (= LF = 0a).
Esiintyykö jossain muualla datassa \r kentän sisällä eli vaikkapa keskellä etunimeä? Jos ei, riittää esitetty tr-muunnos:
tr '\r' '\n' < tiedosto.txt > korjattutiedosto.txt
dos2unixilla voisi kuitenkin siistiä mahdolliset CR+LF:t ensin pois, kuten aiemmin esitin. Muuten niiden tilalle tulee kaksinkertainen rivinvaihto.
-
No nyt :)
Tein nm.sh "skriptin", kun en muutoin osannut tätä ajaa oikeilla parametreillä:
tr '\r' '\n' < $1 > fixit/$1
Kiitokset!
EDIT:
Siis lopullinen ratkaisu:
find . -type f -name "*.txt" -exec ./nm.sh {} \;