[Megoldva] 2 fájl tartalmának "összevegyítése" 1 fájlba

Fórumok

Sziasztok!

Egy olyan kérdésem lenne, hogy ha van a és b fájlom és mondjuk a 2. sora után be akarok szúrni 2 sort b-ből, mindezt egy harmadik fájlba, akkor ezt hogyan tudom megtenni?

pl.

a tartalma:
1
2
3
4
5
6

b tartalma:
11
22
33
44
55
66

Tehát akkor a fentiek alapján szeretnék egy c fájlt, amiben ez áll:
1
2
11
22
3
4
33
44
5
6
55
66

Nagyon köszönöm a segítséget!

Sziasztok!

Hozzászólások

Haaaat, ilyen beepitet parancsrol nem tudok, viszont ezt barmely programnyelven viszonylag egyszeruen meg lehet oldani.

Azert merulnek fel kerdesek: mi legyen, ha a ket file nem egyforma hosszu? mekkorak a file-ok (ha nagyok, akkor celszeru valami buffert kialakitani)? soronkent vagy bajtonkent (esetleg maskepp: ketsoronkent?) kell-e "osszevegyiteni"?

/sza2

Ha a ke't file

a.dat

e's

b.dat

:


awk -v f=b.dat '{ print; if ( n%2 ) { getline < f; print; getline < f; print; } n++; }' a.dat

filea="filea.txt"
fileb="fileb.txt"

nlines=`cat "$filea"|wc -l`

lineno=0

while [ $nlines -ge $lineno ]
do
tail "$filea" -n +$lineno | head -n2>>outputfile
tail "$fileb" -n +$lineno | head -n2>>outputfile
lineno=$(($lineno+2))
done

Nye! Tesztelve, muxik, johet a csoki!

Ordo(n^2)-es, nagy file-oknal ronggya' daralja a diszket, de egyebkent tenyleg jo.

szerk: az olta'son kivul: azert tenyleg megvan ez a hatranya a shell-nek hogy sok esetben relative konnyu megoldast talalni, de az minden lesz, csak nem hatekony... en vmi ket program kozos pipe-ba ontogetes + `cat -n` + `sort -n` komboval gondolkodtam eloszor, de az sem az igazi :] (ott pl tmpfile-ok kellettek volna).

Engem úgy tanították, hogy mielőtt nekiállnék kódolni, gondoljam át a problémát, és készítsek fejben egy megoldást. Ha az megvan, csak akkor nyúljak a géphez.

A vázolt probléma triviális. Meg kell nyitni két fájlt, be kell olvasni két sort az egyikből, ki kell írni, két sort a másikból, azt is ki kell írni, majd ha az egyik véget ér, akkor be kell fejezni a folyamatot. A kérdés, hogy mi legyen a maradék sorokkal, meg mi legyen akkor, ha az egyik file hosszabb mint a másik, de ezek nem voltak definiálva.

Ha így végig van gondolva, akkor a megoldás adja magát, választani kell egy tetszőleges nyelvet, és le kell kódolni. Az egyszerűség kedvéért a bash-t válaszottam mint környezetet, de tény, hogy szinte csak jobb választás van. A kérdés csak az, hogy ezer vagy milliós nagyságrendű sorról van szó, meg hány file, ilyesmik, ez alapján lehet érdemes a nyelvet kiválasztani.

Meg mindig igazad van.
En azert lattam neki a dolognak, mert kivancsi mezei unix parancsokkal hogyan oldhato meg a dolog. Tudom, az awk meg a perl szeles korben elterjedt, megis hasznalatuk sokkal bonyolultabb mint a sima parancsoke. Aztan hamar kimazsolaztam a megoldast, gondoltam posztolok egyet.
A hanyag hozzaallas ebben a kornyezetben (mas hazifeladatat megoldani a weben) nalam elfogadhato :)
Mindenesetre az alabbi "exec" -es megoldasod lenyugozott. Nem is gondoltam volna, hogy igy is lehet :)

Nem szép megoldás, inkább csak érdekességképp:

#!/bin/bash

exec 5< a
exec 6< b

while read Line <&5
do
echo $Line
read Line <&5
echo $Line
read Line <&6
echo $Line
read Line <&6
echo $Line
done

exec 5>&-
exec 6>&-

Kedves Fisher!

Köszönöm a Te megoldásodat is!

Ahogy látom, ezzel a legkönnyebb a manuális beállítása annak, hogy mennyi sort vegyen innen és onnan.

Mégegyszer köszönöm mindenkinek a segítségét, nem házi feladat megoldásnak kell, hanem önállóan próbálok előre jutni unix környezetben és ez sokszor nem egyértelmű.

Valóban, azt kissé elkapkodtam.
Egyébként tényleg fölösleges, a nyitva maradt descriptorokat úgyis lezárja a shell, de így van eleje és vége.

Viszont ami nagyobb baj, hogy az

echo $Line

sor helyett inkább

echo "$Line"

kéne. Vagy inkább echo "${Line}". Ez csak este jutott eszembe, a metrón hazafelé.

Amúgy: http://tldp.org/LDP/abs/html/

Itt a part 5 - 19. rész foglalkozik ilyesmikkel.

Ha egyesével akarod összefésülni, akkor egy "funkcionális" (nem imperatív) megoldás (kettőnél több file-ra is), GNU utility-kkel:


for F in F1 F2 ... Fn; do
  cat --number -- "$F"
done \
| sort --stable --key=1,1n \
| cut --fields=2-

Én is hozzáteszem a magamét, azzal együtt, hogy biztos, hogy a fenti exec-es dolgot használnám. Sajnos ezzel az innen is kettő, onnan is kettő kitétellel eléggé megnehezítetted a dolgot :)


mkfifo 1 2
paste -d X - - < a.txt > 1 &
paste -d X - - < b.txt > 2 &
paste -d X 1 2 | tr X '\n'
rm 1 2

nem szép, de lagalább más, mint az eddigiek. Persze vannak előfeltételek a kódban (pl. a bejövő adatokban ne legyen X, illetve a -d opcióban megadott karaktert úgy kell megválasztani, hogy azzal ne legyen gáz - mondjuk sima szövegfájlnál pl. lapdobás-jel nem nagyon lenne.)


$ paste a.dat b.dat | sed '$!N;s/\n/\t/' | awk 'BEGIN{OFS="\n"} {print $1, $3, $2, $4}'

A valtozatossag kedveert itt egy megoldas perlben, tetszoleges szamu soronkent fesul, tetszoleges szamu file-t, az se zavarja, ha nem egyforma hosszuak a file-ok, vagy valamelyik file-ban a sorok szama nem oszthato n-nel. Nem ugraltatja foloslegesen a diszket, es nem olvassa be a teljes file-okat memoriaba. A hianyzo sorok helyett '<<after-eof>>'-ot printel.

Igy kell hasznalni:

$ comb -n <LINES> FILE1 [FILE2 [FILE3 ...]]

#! /usr/bin/perl

use strict;
use warnings;

use Getopt::Std;
getopts( 'n:', \my %opt );
my $lines_at_once = $opt{n} // 1;

my @file_refs;
for (@ARGV) {
  my $file_ref = { NAME => $_, FH => undef, EOF => 0 };
  open $file_ref->{FH}, '<', $file_ref->{NAME}
    or die "open '$file_ref->{NAME}': $!";
  push @file_refs, $file_ref;
}

while ( grep { not $_->{EOF} } @file_refs ) {
  last if ( @file_refs == grep { eof $_->{FH} } @file_refs );
  for (@file_refs) {
    my $n  = 0;
    my $fh = $_->{FH};
    while ( $n++ < $lines_at_once ) {
      my $line = <$fh>;
      if ( not defined $line ) {
        $_->{EOF} = 1;
        $line = '<<after-eof>>' . $/;
      }
      print $line;
    }
  }
}

__END__

Kedves Rubasov!

A Te megoldásodat is köszönöm, itt is egy dolog foglalkoztat: mi van akkor ha különböző sormennyiségeket akarok beolvasni a bemeneti fájlokból?

Perl-t még annyira sem ismerem mint az Awk-t, de azt gondolom, hogy akkor nem csak egy -n kapcsoló kellene, hanem -n1 -n2 .... ahány fájl van, de a while utáni részt őszintén szólva még nem értem.

Kérhetném, hogy írj oda kommenteket?

Előre is köszönöm!

Itt egy kommentelt valtozat, remelem igy ertheto:


#! /usr/bin/perl

use strict;
use warnings;

use Getopt::Std;

# A '-n' parameter (pozitiv egesz) erteket var, ez az $opt{n}-be kerul.
getopts( 'n:', \my %opt );

# Ha nem volt '-n' a parancssorban, akkor a default legyen '-n 1'.
my $lines_at_once = $opt{n} // 1;

# A file-okkal kapcsolatos infokat egy tombben tartom, aminek az elemei
# hash referenciak, az elso file-ra pl igy neznek ki az elemek:
# $file_refs[0]{NAME} - a file neve
# $file_refs[0]{FH} - file handle
# $file_refs[0]{EOF} - flag valtozo, hogy elertuk-e mar a file veget
# Fontos, hogy a @file_refs a legkulso scope-ban legyen deklaralva, kulonben
# a perl a scope vegen automatikusan meghivja a close($fh)-t.
my @file_refs;

# Minden file argumentumra...
for (@ARGV) {

  # Init $file_refs[$_]
  my $file_ref = { NAME => $_, FH => undef, EOF => 0 };

  # Nyisd meg a file-t olvasasra.
  open $file_ref->{FH}, '<', $file_ref->{NAME}
    or die "open '$file_ref->{NAME}': $!";
  push @file_refs, $file_ref;
}

# Amig van olyan file, aminek meg nem ertunk a vegere...
# A feltetelben szereplo 'grep ...' skalar kontextusban az ilyen file-ok
# szamaval ter vissza.
while ( grep { not $_->{EOF} } @file_refs ) {

  # Ez a sor egy specialis eset kezelese, konkretan:
  # Ha minden file egyforma hosszu es n-nel oszthato szamu sor van bennuk
  # akkor nem eleg utolag ellenorizni, hogy elertuk az EOF-ot, hanem tudnom
  # kell a kulso while ciklus elejen, hogy a kovetkezo olvasas mindegyiken
  # EOF-ot fog adni, pont ezt csinalja az 'eof $fh'.
  last if ( @file_refs == grep { eof $_->{FH} } @file_refs );

  # Mindegyik file-ra...
  for (@file_refs) {

    # Az aktualis n-soros "blokkbol" eddig beolvasott sorok szama.
    my $n  = 0;

    # Ez csak egy segedvaltozo, hogy kesobb ne kapjak syntax errort erre:
    # <$_->{FH}>
    my $fh = $_->{FH};

    # Amig be nem olvastam a szukseges n sort...
    while ( $n++ < $lines_at_once ) {
      my $line = <$fh>;

      # Ha az '<>' operator undeffel tert vissza, akkor elertem a file veget.
      if ( not defined $line ) {

        # Jegyezd ezt meg, hogy a kulso while ciklus a feltetel
        # kiertekelesekor tudhassa.
        $_->{EOF} = 1;

        # Es helyettesitsd a hianyzo sort, hogy a "tablazatos" forma ne
        # seruljon.
        # Ezt persze helyettesithetned ures sorral, vagy tetszoleges olyan
        # ertekkel, ami garantaltan nem szerepel egyik file-odban sem.
        # Illetve, ha peldaul azt szeretned, hogy a kimenet a legrovidebb
        # file-hoz igazodjon, akkor itt kene 'last OUTER WHILE'-t hasznalni,
        # de ehhez egy atmeneti tombben tarolni kene az osszes file utolsonak
        # olvasott "blokkjanak" osszes sorat, hiszen nem tudhatod elore, hogy
        # egy kesobb olvasott file nem bizonyul-e rovidebbnek az aktualisnal.
        # A kimenet legrovidebb file-hoz igazitasa egyebkent utolag is egesz
        # egyszeruen megoldhato, csak csapd le a kimenet veget onnantol, hogy
        # egy a (file-ok szama) * (sorok szama) "blokk"-ban szerepel egy
        # '<<after-eof>>'.
        $line = '<<after-eof>>' . $/;
      }
      print $line;
    }
  }
}

__END__

A masik kerdesedre valaszolva pedig:
Ha file-onkent kulonbozo szamu sort szeretnel fesulni, akkor kb. a kovetkezokre lenne szukseged.
- olyan parancssort dolgozz fel, hogy

$ comb FILE1 N1 [FILE2 N2 [...]]

- ez alapjan vegyel fol egy uj elemet a

$file_refs[$_]

-be, mondjuk igy:

$file_refs[$_]{N} = ...

- majd hasznald ezt az erteket a belso while ciklus felteteleben:

while ( $n++ < $_->{N} )

cat file1.text file2.text |sort -un > file3.text
(bár nem teszteltem)

Kisse unatkoztam, ugyhogy itt van meg egy lehetoseg, azok kedveert, akik ertekelik az ilyet.

Regebben, amikor meg nem ismertem semmilyen epkezlab programozasi nyelvet, az ilyesmi feladatokat tipikusan vi makrokkal oldottam meg. No para, meglepoen egyszeru.

Eloszor generaljunk ket tesztfile-t:


$ for i in $(seq 0 9); do echo a$i >> testa.txt ; echo b$i >> testb.txt ; done

Inditsunk egy vi-t (konkretan nvi-t, mert kisebb valtoztatasokkal ugyan megoldhato vim-ben is, de ott valamiert fajdalmasan lassu).


$ nvi testa.txt

Miutan fut a vi, nyissuk meg osztott ablakban a masik file-t is:


:E testb.txt<CR>

Az ablakok kozott ^w (control+w)-vel valthatunk, valtsunk is vissza az elso ablakra. (A kurzor mindket ablakban az elso sor elso karakteren all.)

Probaljuk ki eloszor, mit csinalnank, hogy az elso ket sort atmasoljuk az elso ablakbol a masodikba:


d2d # ket sort torol es a paste bufferbe tesz
^w # valt a masik ablakba
P # paste-eli a buffer tartalmat az aktualis sor fole
4j # a kurzort oda viszem, ahol a kovetkezo 'P' kovetkezne
^w # vissza az elso ablakba

Ez eddig jo, de ugye nem akarjuk kezzel csinalni? ;-)

Allitsuk vissza a ket file eredeti tartalmat

:e!<CR>

(mindket ablakban), majd irjunk egy rekurziv makrot az elozoekbol:


:map g d2d^wP4j^wg<CR>

Arra ugyeljunk mikozben a fenti makrot visszuk be, hogy bizonyos parancsokat (^w, ^c, ^v, escape) escape-elni kell ^v (control+v)-vel, vagyis egy ^w-t igy vigyunk be: ^v^w.

Minden elkeszult, alljunk az elso file elso sorara a kurzorral, majd futassuk a friss makronkat (nyomjuk meg a 'g'-t).

Ta-daam.

Mar csak el kell mentenunk a masodik ablak tartalmat:

:w out.txt<CR>

Aztan lepjunk ki az ablakokbol mentes nelkul:

:q!<CR>

(ketszer)

Ez a leiras kisse hosszadalmas lett ugyan, de a vi makrok erosen write-onlyk, szoval ha csak a makrot irtam volna le, senki nem ertene, mit is csinal. Valojaban viszont nincs benne semmi bonyolult, csak ismerni kell a vi parancsokat. Es ha lehetek kisse nagyvonalu, hogy nem szamolom az elokeszuleteket, akkor a kod hossza magasan veri az osszes tobbi megoldast.