• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

Zeilen gegeben durch Zeilennummern in Datei ausgeben

ingobulla

Newbie
Hallo,

ich habe eine Datei mit Zeilennummern

Code:
2
5
12
44
501
...

und würde gerne aus einer anderen Datei target.txt die Zeilen mit diesen Zeilennummern ausgeben. Kann man das irgendwie mit sed (oder einem anderen Tool) machen?
 

marce

Guru
Google findet z.B. http://www.linuxforen.de/forums/showthread.php?172815-Bestimmte-Zeile-einer-Textdatei-anhand-Zeilennummer-ausgeben

... und dann z.B. aus "Bereich" einfach eine "Zeilennummer" machen, also z.B.
Code:
z=5 ; sed -ne "${z}p" $datei
 

abgdf

Guru
Code:
#!/usr/bin/perl

use warnings;
use strict;

my $numberfile = "t1";
my $textfile = "t2";

my $fh1; my $fh2;
open($fh1, "<", $numberfile) or die;
my @nums = <$fh1>;
close $fh1;

open($fh2, "<", $textfile) or die;
my @text = <$fh2>;
close $fh2;

foreach my $i (@nums) {
    chomp($i);
    print $text[$i];
}
Bitte noch "t1" und "t2" an die Dateinamen anpassen.
 

abgdf

Guru
marce schrieb:
Hoffentlich ist die Datei < 4GB :)
:mrgreen: Davon bin ich mal ausgegangen. Sonst wär's aber auch kein Problem, dann müßte man halt Tie::File verwenden, das kennt keine Dateigrößenbegrenzung.

Es ist nur so, wenn ich nicht ganz so gängige Perl-Module importiere, kommt von den Fragestellern hier meistens einfach "Das Skript läuft nicht", und dann dauert es ewig, ihnen zu erklären, wie man das Modul installiert. Die meisten springen dann sowieso ab. Insofern bin ich lieber das Risiko eingegangen, daß sie das Skript auf allzugroße Dateien anwenden wollen. Das wäre dann nämlich ihr Problem.
 

marce

Guru
Zugegeben, ich bin mir nicht sicher wie sed das bearbeitet aber die Input-Datei komplett in den Speicher lesen ist ggf. nicht Optimal.
 
A

Anonymous

Gast
sed ist hier nicht sehr praktisch. aber das ist natürlich eine typische Aufgabe um sie sehr effektiv mit awk zu erledigen.
Scriptname = script.awk (Vorsicht ist kein Script das man mit sh ... oder bash ... ausführen kann, weil kein Shellscript sonder ein awk-script)
Code:
#!/usr/bin/awk -f

# Aufruf des scriptes je nachdem ob Script Ausführungsrechte hat oder nicht
#          mit ausführungsrechten geht:
#    ./script.awk zeilennummerdatei textdatei
#          oder wenn keine Ausführungsrechte, dann 
#    awk -f script.awk  zeilennummerdatei textdatei

BEGIN { filename="" }

filename == "" { filename = FILENAME; }
filename == FILENAME {ZEILEN[$1]=1; }
filename != FILENAME {if (FNR in ZEILEN) print  }
robi
 
Damit wir einen sed-Einzeiler haben, würde ich es folgendermassen machen:

Code:
sed -n "$(sed 's#$#p#' filezeilennummer)" target.txt

Haveaniceday
 
A

Anonymous

Gast
haveaniceday schrieb:
Damit wir einen sed-Einzeiler haben, würde ich es folgendermassen machen:
Code:
sed -n "$(sed 's#$#p#' filezeilennummer)" target.txt
Sehr schöne Lösung, ;) mit 3 Lösungen haben wir dann auch gleich mal was zum Härtetest und zum vergleichen.

Prizipiell haben wir hier 3 den gewünschten Ansprüchen voll funktionierende Lösungen gefunden.
Die SED Lösung hat 2 winzige kleine Nachteile die aber erst bei sehr großen fielezeilennumme-Dateien zum tragen kommen werden. mit steigender Anzahl der zu suchenden Zeilennummern wird sed immer langsamer und erzeugt wenn die Zeilennummerdatei zu lang wird, einen Fehler auf der Bash.
BASH schrieb:
bash: /usr/bin/sed: Die Argumentliste ist zu lang

die hier gezeigte Perl Lösung ist bedeuted schneller. Läuft auf eine (wenn auch ungeollt und unspezifizierte) Warnung wenn die Zeilennummern in der Zeilennummerdatei größer sind als die tatsächliche Anzahl der Zeilennummern in der target.txt
PERL schrieb:
Use of uninitialized value in print at ./test.perl line 20.
Die Perl Lösung unterscheidet sich gegen die SED und AWK Lösung noch darin, dass die Zeilen in der Reihenfolge ausgegeben werden in der sie in der Datei stehen. SED und AWK geben die Zeilen in der Reihenfolge der target.txt aus, egal wie die Zeilennummern in der Zeilennummerndatei sortiert sind. Allerdings bei der SED-Lösung werden diese dann mehrfach hintereinander ausgegeben wenn sich Zeilennummern in der Liste wiederholen würden. Bei der Perl-Lösung wäre also somit auch sichergestellt, wenn Zeilennummern mehrfach vorkommen werden sie auch mehrfach an den entsprechenden Stellen ausgegeben.

AWK wie immer typisch bei solchen Aufgaben noch ein ganzes Pfund schneller als Perl ;)

Damit kann man mittels der festgestellten zusätzlichen Eigenschaften der 3 Lösungen durchaus auch Empfehlungshilfen ableiten.

SED-Lösung ist dort richtig wo sichergestellt ist, dass die Zeilennummern eindeutig sind und die Anzahl der gesuchten Zeilennummern nicht explosiv ansteigen kann. Auch ist eine einfache Erweiterung möglich um die target.txt direkt mit dem Script auf die Zeilennummern aus der Liste zu kürzen.

Perl-Lösung ist dort richtig, wo es darauf ankommt die Zeilen genau in der Reihenfolge der Zeilennummern zu bekommen,

AWK-Lösung ist dort richtig wo die Reihenfolge der Ausgabe der Reihenfolge der Orginaldatei entsprechen soll und es dabei vollkommen egal ist wie die Zeilennummerndatei sortiert ist, und ob dort eventuell durch mehrfachläufe von Scripten die die Zeilennummern zusammengestellt haben eventuell doppelte Einträge vorhanden sind.

wer's selbst testen will.
Code:
robi@lux001:~/test> for i in $(seq 1 500000); do echo das ist die Zeile $i in der Datei; done > target.txt
robi@lux001:~/test> for i in $(seq 2 250 499999 ); do echo $i ; done > filezeilennummer 
robi@lux001:~/test> time ./test.perl >/dev/null

real    0m0.142s
user    0m0.112s
sys     0m0.030s
robi@lux001:~/test> time ./test.perl >/dev/null

real    0m0.141s
user    0m0.107s
sys     0m0.034s
robi@lux001:~/test> time sed -n "$(sed 's#$#p#' filezeilennummer)" target.txt >/dev/null

real    0m1.904s
user    0m1.898s
sys     0m0.004s
robi@lux001:~/test> time sed -n "$(sed 's#$#p#' filezeilennummer)" target.txt >/dev/null

real    0m1.748s
user    0m1.741s
sys     0m0.004s
robi@lux001:~/test> time ./test.awk filezeilennummer target.txt > /dev/null

real    0m0.101s
user    0m0.097s
sys     0m0.004s
robi@lux001:~/test> time ./test.awk filezeilennummer target.txt > /dev/null

real    0m0.094s
user    0m0.089s
sys     0m0.004s
robi@lux001:~/test>

Na ich denke mal, besser kann man ein solches Thema nicht abarbeiten ;)

robi
 
Oben