Kirjoittaja Aihe: Tiettyjen rivien poimiminen tekstitiedostosta  (Luettu 3192 kertaa)

teele

  • Käyttäjä
  • Viestejä: 818
    • Profiili
Tiettyjen rivien poimiminen tekstitiedostosta
« : 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?


SuperOscar

  • Käyttäjä
  • Viestejä: 4000
  • Ocatarinetabellatsumtsum!
    • Profiili
    • Legisign.org
Vs: Tiettyjen rivien poimiminen tekstitiedostosta
« Vastaus #1 : 06.12.15 - klo:14.18 »
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:

Koodia: [Valitse]
#!/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.
pöytäkone 1, kannettavat 1–3: Debian GNU/Linux 12; pöytäkone 2: openSUSE Tumbleweed; NUC: openSUSE Leap 15.5; RPi 1: FreeBSD 14-RELEASE; RPi 2: LibreELEC 11

AimoE

  • Käyttäjä
  • Viestejä: 2719
    • Profiili
Vs: Tiettyjen rivien poimiminen tekstitiedostosta
« Vastaus #2 : 06.12.15 - klo:14.22 »
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.

teele

  • Käyttäjä
  • Viestejä: 818
    • Profiili
Vs: Tiettyjen rivien poimiminen tekstitiedostosta
« Vastaus #3 : 06.12.15 - klo:15.11 »
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ä
« Viimeksi muokattu: 06.12.15 - klo:15.12 kirjoittanut teele »

SuperOscar

  • Käyttäjä
  • Viestejä: 4000
  • Ocatarinetabellatsumtsum!
    • Profiili
    • Legisign.org
Vs: Tiettyjen rivien poimiminen tekstitiedostosta
« Vastaus #4 : 06.12.15 - klo:16.14 »
Jollei tuo AWK-versio toimi (eli tietueita ei ole erotettu tyhjin rivein), entäs tämä:

Koodia: [Valitse]
#!/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]))
pöytäkone 1, kannettavat 1–3: Debian GNU/Linux 12; pöytäkone 2: openSUSE Tumbleweed; NUC: openSUSE Leap 15.5; RPi 1: FreeBSD 14-RELEASE; RPi 2: LibreELEC 11

teele

  • Käyttäjä
  • Viestejä: 818
    • Profiili
Vs: Tiettyjen rivien poimiminen tekstitiedostosta
« Vastaus #5 : 07.12.15 - klo:21.13 »
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.

Koodia: [Valitse]
/* 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ä

AimoE

  • Käyttäjä
  • Viestejä: 2719
    • Profiili
Vs: Tiettyjen rivien poimiminen tekstitiedostosta
« Vastaus #6 : 07.12.15 - klo:22.40 »
Itse turvaudun perliin tällaisissa tapauksissa.

Esimerkiksi jos tiedoston nimi annetaan komentorivillä:

Koodia: [Valitse]
perl jaa.pl koedata.tks
niin koodi tiedostossa jaa.pl voisi olla vaikkapa:

Koodia: [Valitse]
#!/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ä.
« Viimeksi muokattu: 07.12.15 - klo:22.47 kirjoittanut AimoE »

SuperOscar

  • Käyttäjä
  • Viestejä: 4000
  • Ocatarinetabellatsumtsum!
    • Profiili
    • Legisign.org
Vs: Tiettyjen rivien poimiminen tekstitiedostosta
« Vastaus #7 : 07.12.15 - klo:23.10 »
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ä:

Koodia: [Valitse]
#!/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).

Koodia: [Valitse]
import std.stdio;

void main()
{
    auto counter = 0;

    foreach (line; stdin.byLine) {
        ++ counter;
        if ((counter % 20 == 7) || (counter % 20 == 12))
            writeln(line);
    }
}
« Viimeksi muokattu: 07.12.15 - klo:23.19 kirjoittanut SuperOscar »
pöytäkone 1, kannettavat 1–3: Debian GNU/Linux 12; pöytäkone 2: openSUSE Tumbleweed; NUC: openSUSE Leap 15.5; RPi 1: FreeBSD 14-RELEASE; RPi 2: LibreELEC 11

_Pete_

  • Käyttäjä
  • Viestejä: 1836
  • Fufufuuffuuu
    • Profiili
Vs: Tiettyjen rivien poimiminen tekstitiedostosta
« Vastaus #8 : 08.12.15 - klo:12.23 »
Tällaisen sai väkerrettyä pelkkää bash:ia käyttäen:

Koodia: [Valitse]
#!/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:

Koodia: [Valitse]



kohdenimi
kohdenimitieto1
kohdenimitieto2
kohdenimitieto3


kohdenimi2
kohdenimi2tieto1
kohdenimi2tieto2
kohdenimi2tieto3
kohdenimi2tieto2
kohdenimi2tieto3
kohdenimi2tieto2
kohdenimi2tieto3







Ajo rr.sh

Koodia: [Valitse]
$ ./rr.sh data.txt
>> kohdenimi
kohdenimitieto1
kohdenimitieto2
>> kohdenimi2
kohdenimi2tieto1
kohdenimi2tieto2


Idea siis on poimia tekstitiedostosta peräkkäiset ei tyhjät rivit yhteen blokkiin ...


petteriIII

  • Käyttäjä
  • Viestejä: 663
    • Profiili
Vs: Tiettyjen rivien poimiminen tekstitiedostosta
« Vastaus #9 : 09.12.15 - klo:01.10 »
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:
Koodia: [Valitse]
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 ):
Koodia: [Valitse]
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ä:
Koodia: [Valitse]
sed -r 's/^(.)\1{1,}$//g' tiedosto > delmeee; mv delmeee tiedosto

Tiedostonimet: delmeeXX on aika tyly. Ne voi muuttaa asennuspaikan nimeksi käskyllä:
Koodia: [Valitse]
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:
Koodia: [Valitse]
sed -i 's/kohde[0-9]*/\n&/' tiedosto  # siis tuon: kohde[0-9]* tilalle kirjoitetaan asiakkaita kuvaavan rivin joku ominaisuus mitä ei ole muualla
« Viimeksi muokattu: 10.12.15 - klo:20.26 kirjoittanut petteriIII »