Ubuntu Suomen keskustelualueet
Ubuntun käyttö => Ohjelmointi, palvelimet ja muu edistyneempi käyttö => Aiheen aloitti: teele - 06.12.15 - klo:13.59
-
Tekstimuotoinen tiedosto sisältää osoite- ja rakennustietoja useasta kohteesta siten, että jokainen tieto on omalla rivillään.
Tiedostossa on tiedot noin 50 (kohdelkm) kohteesta, ja kaikki kohdetiedot ovat samanmuotoisia.
Jokaisesta kohteesta on noin 20 (tietolkm) tietoriviä.
Tiedosto näyttää siis suunnilleen tällaiselta
kohdenimi
tieto1
tieto2
tieto3
...
kohdenimi
tieto1
tieto2
tieto3
....
kohdenimi
tieto1
tieto2
tieto3
....
Miten saisin poimittua jokaisesta kohteesta (k) rivit (m ja n) omaan tiedostoonsa.
Komentoriviohjelmointia en osaa vielä niin paljon, että poiminta onnistuisi helposti.
Ehkä python olisi myös yksi ratkaisuvaihtoehto?
-
Python tai AWK käyvät, mutta tämmöisten tapausten harmina on se, että UNIXin ohjelmointityökalut ovat keskittyneet löytämään asioita riviltä, ei peräkkäisiltä riveiltä.
Periaatteessa AWKissa voi säätää tietue- ja kenttäerottimia, mutta se ei välttämättä toimi. Jos kuitenkin on niin, että tietueita erottaa tyhjä rivi eikä tyhjiä riviä ole kenttien välissä, voisit kokeilla:
#!/usr/bin/gawk -f
BEGIN {
RS = "\n\n" # Asetetaan tietuerotin: tyhjä rivi
FS = "\n" # Asetetaan kenttäerotin: rivinvaihto
}
# Tulostetaan kentät 1 (= kohdenimi), 2 ja 3
{ print $1, $2, $3 }
Jos tuo ei toimi, kannattanee siirtyä Pythoniin. Suurin ongelmasi on erottaa uuden tietueen alku eli kohdenimi-rivit, jollei tietueita ole erotettu siististi tyhjillä riveillä toisistaan.
-
Tiedosto näyttää siis suunnilleen tällaiselta
Kuvaukesta ei käy riittävästi ilmi millä tavalla kohderivit poikkeavat tietoriveistä. Kerro lisää.
Pahimmassa tapauksessa, jos tietoriveillä ei ole mitään merkkijonoa tai rakennetta jolla ne voisi erottaa tietoriveistä, joudut pikkuisen käsin editoimaan tiedostoa, esimerkiksi niin että ryhmien välillä on tyhjä rivi (ja muualla niitä ei ole). Silloin erottelu onnistuu. Mutta parempi olisi että muokkausta ei tarvittaisi. Kerro siis enemmän siitä missä muodossa kohderivit ja tietorivit ovat.
Jaa, sekin tieto riittäisi jos rivejä on joka ryhmässä aina sama vakiomäärä, eli jos tietorivejä on aina samat 20 riviä, riippumatta siitä onko ne tyhjiä vai ei, niin sekin on ihan riittävä apu ohjelmoituun jakoon.
-
juu, tietorivejä kustakin kohteesta on aina sama vakiomäärä (v) kappaletta, ja sitten ne poimittavat rivit ovat aina vakiopaikoilla (m ja n) lukien kunkin kohdetietueen alusta, jossa siis v kappaletta rivejä yhteensä
-
Jollei tuo AWK-versio toimi (eli tietueita ei ole erotettu tyhjin rivein), entäs tämä:
#!/usr/bin/env python3
import sys
lines_for_rec = 5 # Montako riviä per tietue (kohdenimi mukaan luettuna)
for arg in sys.argv[1:]:
with open(arg, 'r') as infile:
data = [line.rstrip() for line in infile if line.rstrip()]
for i in range(0, len(data), lines_for_rec):
name, *fields = data[i:i + lines_for_rec]
# Tulostetaan kohdenimi ja kentät 2–3 (huomaa numerointi fields-muuttujassa!)
print('kohdenimi="{}", kenttä 2="{}", kenttä 3="{}"'.format(name,
fields[1], fields[2]))
-
Kiitos hyvistä vihjeistä :)
Koska en oikein osaa pythonia, jouduin c++ -hommiin. Tällainen ohjelmapätkä näyttää ainakin alustavasti toimivan. Se poimii 20 riviä sisältävistä kohdetiedoista rivit 7 ja 12.
/* rivipoiminta01.cpp */
#include <iostream>
#include <fstream>
#include <string.h>
int main(int argc, char *argv[])
{
std::ifstream infile("koedata.tks");
std::string str;
int i = 0, j = 0;
while (std::getline(infile, str))
{
i++; j++;
if(i == 20)
{ i = 0;
j = 0;
continue;
}
if(j == 12 || j == 7)
{ std::cout << str << '\n'; // tulostettavat rivit
}
};
std::cout << str << '\n';
return(0);
}
Mutta vieläkin tuntuu, että kätevämmin olisi mennyt jollain komentorivipätkällä
-
Itse turvaudun perliin tällaisissa tapauksissa.
Esimerkiksi jos tiedoston nimi annetaan komentorivillä:
perl jaa.pl koedata.tks
niin koodi tiedostossa jaa.pl voisi olla vaikkapa:
#!/usr/bin/perl
use strict;
use warnings;
my $n = 0;
my $orig = $ARGV[0];
open(IN, $orig) or die "Tiedoston $orig avaaminen ei onnistu: $^E\n";
while ($_ = <IN>) {
my $name = sprintf "Kohde_%02u_$orig", ++$n;
open(OUT, '>', $name) or die "Tiedoston $name avaaminen ei onnistu: $^E\n";
print OUT $_;
for my $r (1..20) {
defined($_ = <IN>) or die "
Kohteen $n tietorivejä oli luettu $r kpl, kun
tiedoston $orig luku keskeytyi: $^E
";
print OUT $_;
}
close OUT or die "Tiedoston $orig kirjoitus ei onnistu: $^E\n";
}
close IN;
Olen siis tässä yrittänyt ottaa huomioon myös virhekäsittelyn. Ilman sitä olisin ehkä yrittänytkin jotain sh-scriptiä, mutta ongelman asettelu selvästi vaati sitä että virheetkin käsitellään.
Output-tiedoston nimeäminen on tuossa ihan raakile - se menee heti rikki jos tiedoston nimessä on myös hakemisto mukana. Kaikki muut omituisuudet ovat perlin omituisuuksia.
Edit: korjattu virhe kohdenumeron käsittelyssä.
-
Jos tietueessa on rivejä 20 ja halutaan rivit 7 ja 12 (laskien 1:stä alkaen), niin Pythonissa päästään näinkin vähällä:
#!/usr/bin/env python
with open('koedata.tks', 'r') as infile:
for counter, line in enumerate(infile):
line = line.rstrip()
if (counter + 1) % 20 in (7, 12):
print(line)
(Jos rivejä eli kenttiä lasketaan 0:sta eikä 1:stä alkaen, niin toiseksi viimeisellä rivillä ”(counter + 1) % 20” muutetaan muotoon ”counter % 20”.)
Muoks: Heh, tässä D:tä opiskellessa tuli tehtyä tämmöinenkin. Lukee stdiniä. Olettaa, ettei tyhjiä riviä tietueitten välissä, ja joka tietueesta tulostetaan kentät 6 ja 11 (eli rivit 7 ja 12).
import std.stdio;
void main()
{
auto counter = 0;
foreach (line; stdin.byLine) {
++ counter;
if ((counter % 20 == 7) || (counter % 20 == 12))
writeln(line);
}
}
-
Tällaisen sai väkerrettyä pelkkää bash:ia käyttäen:
#!/usr/bin/bash
block=false
blockText=""
while IFS='' read -r line || [[ -n "$line" ]]; do
# echo "$block > $line"
if [ -z "$line" ]; then
if [ "$block" = "false" ]; then
nop=""
else
if [ -z "$blockText" ]; then
nop=""
else
# blockText contains continues block of lines separated |
echo ">> $blockText" | cut -d'|' -f1
echo "$blockText" | cut -d'|' -f2
echo "$blockText" | cut -d'|' -f3
blockText=""
fi
fi
else
if [ "$block" = "false" ]; then
block=true
blockText="$line"
# echo "#3"
else
if [ -z "$blockText" ]; then
blockText="$line"
else
blockText="$blockText|$line"
fi
# echo "#4"
fi
fi
done < "$1"
Testi tiedosto:
kohdenimi
kohdenimitieto1
kohdenimitieto2
kohdenimitieto3
kohdenimi2
kohdenimi2tieto1
kohdenimi2tieto2
kohdenimi2tieto3
kohdenimi2tieto2
kohdenimi2tieto3
kohdenimi2tieto2
kohdenimi2tieto3
Ajo rr.sh
$ ./rr.sh data.txt
>> kohdenimi
kohdenimitieto1
kohdenimitieto2
>> kohdenimi2
kohdenimi2tieto1
kohdenimi2tieto2
Idea siis on poimia tekstitiedostosta peräkkäiset ei tyhjät rivit yhteen blokkiin ...
-
Tuo SuperOscarin ehdottama on helpoin ja nopein ja tekee tuon saman mutta se on kuvatunlaisena liukas käsiteltävä ja vaatii käytännössä pari varmistusta. Koodina koko ohjelma ilman varmistuksia on:
awk -v RS='' '{ print $0 > "/tmp/delmee"NR }' tiedosto # jako tiedostoiksi tyhjien rivien kohdilta
- mutta ennenkuin siitä saa käyttökelpoisen niin täytyy tehdä varmistuksia:
ensimmäinen varmistus on se, että muutetaan ihmisen mielestä tyhjät rivit myös koneen mielestä tyhjiksi ( siis jos rivillä on pelkästään välilyöntejä ja TAB:eja niin rivi muutetaan tyhjäksi ):
sed -i 's/^[[:space:]]*$//g' tiedosto; awk -v RS='' '{ print $0 > "/tmp/delmee"NR }' tiedosto # jos rivi on pelkkää välilyöntiä niin se tyhjätään
joskus kohdepaikkojen eroittimena on rivi, joka muodostuu pelkästään samasta merkistä; näitä merkkejä voi olla epämääräinen määrä mutta kuitenkin vähintään 2.Tällaiset rivit voidaan muuttaa tyhjiksi käskyllä:
sed -r 's/^(.)\1{1,}$//g' tiedosto > delmeee; mv delmeee tiedosto
Tiedostonimet: delmeeXX on aika tyly. Ne voi muuttaa asennuspaikan nimeksi käskyllä:
for n in /tmp/delmee[0-9]*; do mv $n /tmp/$(head -n 1 $n); done
Joskus taas asiakkaiden tietoja ei erota mikään vaan luotetaan siihen että kun ihminen lukee tiedostoa on aivan selvää missäkohtaa asiakas muuttuu. Mutta kone ei tämmöistä osaa ellei neuvota mitenkä asiakkaat erotetaan. Erotuskäskyksi kelpaa esimerkiksi:
sed -i 's/kohde[0-9]*/\n&/' tiedosto # siis tuon: kohde[0-9]* tilalle kirjoitetaan asiakkaita kuvaavan rivin joku ominaisuus mitä ei ole muualla