it-swarm.com.de

Sed Skript stürzt bei großer Datei ab

Ich habe ein Shell-Skript, das im Wesentlichen ein sed-Skript mit einigen Überprüfungen ist. Das Ziel des Skripts ist es, den Header einer Datei von zu konvertieren.

&FCI
NORB=280,
NELEC=78,
MS2=0,
UHF=.FALSE.,
ORBSYM=1,1,1,1,1,1,1,1,<...>
&END
  1.48971678130072078261E+01   1   1   1   1
 -1.91501428271686324756E+00   1   1   2   1
  4.38796949990802698238E+00   1   1   2   2

zu

&FCI NORB=280, NELEC=78, MS2=0, UHF=.FALSE., 
ORBSYM=1,1,1,1,1,1,1,1,<...>
ISYM=1,
/
  1.48971678130072078261E+01   1   1   1   1
 -1.91501428271686324756E+00   1   1   2   1
  4.38796949990802698238E+00   1   1   2   2

Dies ist das Skript:

#!/bin/bash

# $1 : FCIDUMP file to convert from "new format" to "old format"

if [ ${#} -ne 1 ]
then
  echo "Syntaxis: fcidump_new2old FCIDUMPFILE" 1>$2
  exit 1
fi

if egrep '&FCI ([a-zA-Z2 ]*=[0-9 ]*,){2,}' ${1} > /dev/null
then
  echo "The provided file is already in old FCIDUMP format." 1>&2
  exit 2
fi

sed '
1,20 {
   :a; N; $!ba
   s/\(=[^,]*,\)\n/\1 /g
   s/\(&FCI\)\n/\1 /
   s/ORBSYM/\n&/g
   s/&END/ISYM=1,\n\//
}' -i "${1}"

exit 0

Dieses Skript funktioniert für "kleine" Dateien und jetzt bin ich auf eine Datei von ca. 9 Gigabyte gestoßen und das Skript stürzt mit der "super clear error message" ab:

script.sh: line 24: 406089 Killed                  sed '
1,20 {
   :a; N; $!ba
   s/\(=[^,]*,\)\n/\1 /g
   s/\(&FCI\)\n/\1 /
   s/ORBSYM/\n&/g
   s/&END/ISYM=1,\n\//
}' -i "${1}"

Wie kann ich dieses sed-Skript so einstellen, dass es wirklich nur den Header anzeigt und so große Dateien verarbeiten kann? Die hässliche "20" ist übrigens da, weil ich etw nicht besser kenne.

Zusatzinformation:

  • nachdem ich einige Dinge ausprobiert hatte, sah ich, dass diese seltsamen Dateien erzeugt wurden: sedexG4Lg, sedQ5olGZ, sedXVma1Y, sed21enyi, sednzenBn, sedqCeeey sedzIWMUi. Alle waren leer, bis auf sednzenBn, das nur die Hälfte der Eingabedatei enthielt.

  • wenn Sie das Flag -i verwerfen und die Ausgabe in eine andere Datei umleiten, erhalten Sie eine leere Datei.

6
Josja

Allgemeine Methode

  • Sie können jede Datei in einen Header und eine zweite Datei mit den Datenzeilen aufteilen
  • Dann können Sie einen Header mit Ihrem aktuellen sed-Befehl ganz einfach separat bearbeiten
  • Schließlich können Sie den Header und die Datei mit den Datenzeilen verketten.

Leichte Tools zur Verwaltung großer Dateien

Prüfung

  • Ich habe mit Ihrem Header und einer Datei mit 1080000000 nummerierten Zeilen (Größe 19 Gib) insgesamt 1080000007 Zeilen getestet und es hat funktioniert, die Ausgabedatei (mit 1080000004 Zeilen) wurde in 5 Minuten auf meiner alten HP XW8400 Workstation geschrieben (einschließlich der Eingabe des Befehls) um das Shellscript zu starten).

    $ ls -lh --time-style=full-iso huge*
    -rw-r--r-- 1 sudodus sudodus 19G 2018-12-15 19:50:45.278328120 +0100 huge.in
    -rw-r--r-- 1 sudodus sudodus 19G 2018-12-15 19:55:46.808798456 +0100 huge.out
    
  • Die großen Schreibvorgänge erfolgten zwischen der Systempartition auf einer SSD und einer Datenpartition auf einer Festplatte.

Shell-Skript

Sie benötigen genügend freien Speicherplatz im Dateisystem, in dem Sie /tmp für die riesige temporäre 'Daten'-Datei haben, entsprechend Ihrer ursprünglichen Frage mehr als 9 GB.

$ LANG=C df -h /tmp
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       106G   32G   69G  32% /

Dies scheint eine umständliche Methode zu sein, funktioniert jedoch für große Dateien, ohne dass die Tools abstürzen. Möglicherweise müssen Sie die temporäre 'Daten'-Datei an einer anderen Stelle speichern, z. B. auf einem externen Laufwerk (aber wahrscheinlich langsamer).

#!/bin/bash

# $1 : FCIDUMP file to convert from "new format" to "old format"

if [ $# -ne 2 ]
then
  echo "Syntaxis: $0 fcidumpfile oldstylefile " 1>&2
  echo "Example:  $0 file.in file.out" 1>&2
  exit 1
fi

if [ "$1" == "$2" ]
then
  echo "The names of the input file and output file must differ"
  exit 2
exit
fi

endheader="$(grep -m 1 -n '&END' "$1" | cut -d: -f1)"
if [ "$endheader" == "" ]
then
  echo "Bad input file: the end marker of the header was not found"
  exit 3
fi
#echo "endheader=$endheader"

< "$1" head -n "$endheader" > /tmp/header
#cat /tmp/header

if egrep '&FCI ([a-zA-Z2 ]*=[0-9 ]*,){2,}' /tmp/header  > /dev/null
then
  echo "The provided file is already in old FCIDUMP format." 1>&2
  exit 4
fi

# run sed inline on /tmp/header 
sed '
{
:a; N; $!ba
s/\(=[^,]*,\)\n/\1 /g
s/\(&FCI\)\n/\1 /
s/ORBSYM/\n&/g
s/&END/ISYM=1,\n\//
}' -i /tmp/header 

if [ $? -ne 0 ]
then
  echo "Failed to convert the header format in /tmp/header"
  exit 5
fi

< "$1" tail -n +$(($endheader+1)) > /tmp/tailer

if [ $? -ne 0 ]
then
  echo "Failed to create the 'data' file /tmp/tailer"
  exit 6
fi

#echo "---"
#cat /tmp/tailer
#echo "---"

cat /tmp/header /tmp/tailer > "$2"

exit 0
4
sudodus

sed ist wahrscheinlich NICHT das beste Werkzeug dafür, untersuchen Sie Perl. Sie können das Problem jedoch wie folgt umschreiben:

  1. Extrahieren Sie den alten Header aus der riesigen Datendatei in eine eigene Datei.

  2. Passen Sie den extrahierten alten Header an, um ihn zum neuen Header zu machen.

  3. Ersetzen Sie den alten Header durch den neuen Header in der riesigen Datendatei.

    endheader="$(grep -m 1 -n '&END' "$1" | cut -d: -f1)"
    head -n "$endheader" >/tmp/header
    trap "/bin/rm -f /tmp/header" EXIT
    # do the sed stuff to /tmp/header, I assume it does what you want 
    sed '
    {
    :a; N; $!ba
    s/\(=[^,]*,\)\n/\1 /g
    s/\(&FCI\)\n/\1 /
    s/ORBSYM/\n&/g
    s/&END/ISYM=1,\n\//
    }' -i /tmp/header 
    
    # Then combine the new header with the rest of the giant data file,
    # using `ed` (see `man ed;info Ed`) and here-document
    ed "$1" <<EndOfEd
    1,${endheader}d
    :0r /tmp/header
    :wq
    EndOfEd
    
0
waltinator