Potrzebny skrypt

Zaczęty przez krzyszp, 05 Czerwiec 2017, 18:51

krzyszp

Szukam pomocy w napisaniu skryptu, który dla wszystkich plików w katalogu wg wzorca nazwy podmieni co 7 linię (ta linia jest pusta) na jakiś znak.

Generalnie, w plikach wynikowych Universe dane wyglądają tak:
14.048961 13 13 1.108065 1.593916 19.555800 0.598142 13.140188 14.048960 1
22.742557 0.000000 21.118602 16.010919 -100000 673
9.087724e-001 9.613467e+005 1.491 -0.039 0.232 21.848 3.207 -10.463
1.309 7.358 -1 7 8.928 0.000 13.140 1.108 0.000 0
1.108 6.232 13 8 10.854 0.157 14.049 1.594 0.000 0
5 MT1(1-1) MT1(2-1) CE2(8-2;13-7) SN1 SN2

10.220098 13 14 1.270304 7.411970 13.987911 0.094854 8.868781 10.220097 1
32.717507 0.000000 26.523457 22.632275 -100000 39457
1.351316e+000 6.818403e+004 -1.748 -0.178 0.386 -2.244 -0.215 0.713
4.284 32.105 8 1 353.330 0.000 8.869 1.233 0.037 0
1.270 8.236 13 8 12.661 0.000 10.220 7.412 0.000 0
6 MT1(1-1) MT1(2-1) MT1(4-1) SN1 CE2(13-2;13-7) SN2

33.506120 13 13 1.159634 1.107936 3.329772 0.780745 25.299691 33.506119 1
161.207612 0.596501 11.037217 8.659463 -100000 42576
8.206429e+000 2.425411e+002 -0.959 0.174 -0.418 1.195 -1.851 9.031
2.365 12.857 8 1 408.865 0.000 25.300 1.108 0.051 0
1.160 1.950 13 -1 0.734 0.000 33.506 1.108 0.000 0
5 MT1(2-1) SN1 CE2(13-4;13-7) CE2(13-8;13-13) SN2

38.589364 13 13 1.274492 1.107936 1827.260819 0.999000 35.938763 38.589363 1
134.238697 0.498906 9.136170 8.890891 -100000 46017
2.650600e+000 3.021633e+005 0.038 -0.030 -0.038 2.259 5.572 -11.232
1.401 12.513 9 1 911.060 0.000 35.939 1.260 0.014 1
1.274 3.070 13 8 50.522 0.000 38.589 1.108 0.000 0
6 MT1(2-1) MT1(8-1) MT1(9-1) SN1 CE2(13-4;13-7) SN2


Jak widać, dane są w blokach po 6 linijek. Te linijki muszę połączyć w jedną linię, jako jeden rekord, następnie drugi blok w następną linię itd...
Takich plików mam grube miliony, więc szybkość operacji ma niebagatelne znaczenie...


Należę do drużyny BOINC@Poland
Moja wizytówka

apohawk

Nie mam teraz dostępu do linuksa w domu, ale spróbowałbym z awk. Jeśli dobrze pamiętam, to jak mu podać znak nowej linii jako separator pola, to na pustej linii skończy rekord. Takie specjalne, dziwne zachowanie.
Coś takiego:
awk -F'\n' -- '{ print $0 }'


albo bardziej topornie:
awk -- 'BEGIN { i=0 }
{ if($0=="") {
  print x[0] " " x[1] " " x[2] " " x[3] " " x[4] " " x[5] " " x[6]
  i=0
}
else {
x[i]=$0
i=i+1
}
END { print x[0] " " x[1] " " x[2] " " x[3] " " x[4] " " x[5] " " x[6] }'

Do zweryfikowania, czy tablice się tak robiło w awk.

[EDIT]
Przeczytałem jeszcze raz. Jak chcesz po prostu zamienić pustą linię na coś innego, to może
sed -i 's/^$/moje znaki/g' moje pliki wg wzorca
No good deed goes unpunished.

krzyszp

Spłodziłem taki skrypt:

#!/bin/bash
cd data
now=$(date +"%T")
echo "Current time : $now"

echo 'przenosze'
for f in *; do mv "$f" "$f.gz"; done
echo 'rozpakowuje'
gunzip *.gz
rm *.gz

echo 'rozpakowane, zaczynam czytac parametry'
now=$(date +"%T")
echo "Current time : $now"

for filename in *_3
do
name=${filename##*/}
#name=${name::-1}
#echo $name >> $filename
        tr '\n' ',' < $filename > plik.txt
echo $name >> plik.txt
        mysql -u root -pXXX --local-infile universe-results -e "LOAD DATA LOCAL INFILE 'plik.txt'  INTO TABLE bhspin2param  FIELDS TERMINATED BY ',' Lines TERMINATED BY '\n'"
rm plik.txt
done
echo 'parametry wczytane, wgrywam dane'
now=$(date +"%T")
echo "Current time : $now"

for filename in *_0
do
name=${filename##*/}
base=${name%1}
#base2=${name::-1}
cat $filename | tr -s ' ' >> "${base}.txt"
awk 'NR % 7 !=0 {printf $0;printf ""} NR % 6 ==0 {print "END"}' ${base}.txt >> ${base}P.txt
tr -d "\n\r" < ${base}P.txt >> ${base}C.txt
sed s/END/\\n/g ${base}C.txt >> ${base}R.txt
# tutaj wstawic nazwe pliku do kazdej linii
mysql -u root -pXXX --local-infile universe-results -e "LOAD DATA LOCAL INFILE '${base}R.txt'  INTO TABLE bhspin2data  FIELDS TERMINATED BY ' ' LINES TERMINATED BY '\n'"
rm ${base}R.txt
rm ${base}P.txt
rm ${base}C.txt
rm ${base}.txt
done
echo 'koniec wgrywania'
now=$(date +"%T")
echo "Current time : $now"

No i teraz zależy mi, żeby po linii
# tutaj wstawic nazwe pliku do kazdej linii
muszę wstawić nazwę pliku aktualnie obrabianego, z uciętym ostatnim znakiem w nazwie (bez ścieżki dostępu).

Pierwsza pętla obrabia inny plik, ale właśnie "styczną" dla nich przy zapisie do bazy jest nazwa pliku.


Należę do drużyny BOINC@Poland
Moja wizytówka

apohawk

Jak patrzę na ten skrypt, to nie do końca ogarniam, co z tym robisz  %) więc wracam do tego:
CytatJak widać, dane są w blokach po 6 linijek. Te linijki muszę połączyć w jedną linię, jako jeden rekord, następnie drugi blok w następną linię itd...
Po sprawdzeniu proponuję fragment podobny do tego z mojej pierwszej odpowiedzi:
awk -- 'BEGIN { FS="\n"; RS="" } { $1=$1; print $0 }'
Dla danych
11
12
13
14
15
16

21
22
23
24
25
26


Zwróci
11 12 13 14 15 16
21 22 23 24 25 26
Sprawdzałem dla miliona takich bloków/rekordów, wykonywało się ok. 0.5s.

RS to record separator, a ustawiony na pusty oznacza, że separatorem rekordu jest pusta linia. Konieczne jest to $1=$1, aby awk zamieniło \n na spacje (IFS na OFS).

Jak patrzę na twój skrypt, to mam wrażenie, że więcej tam się dzieje, niż piszesz  %)
Np. ten END. Jakby wstawiany w 6. linii, a potem zamieniany na nową linię. Nie możesz tego END od razu zamienić na nazwę pliku z usuniętym końcowym / (np. sed s=/$== lub basename filename, nie pamiętam, jak to poprawnie zrobić w zmiennej basha)? np. sed "s/END/\n`basename ${filename}`/g"?

Może będzie łatwiej, jak dla tego zbioru danych z 1. posta podasz format, jakiego oczekujesz? :) Chętnie spróbuję pomóc. Lubię pomagać ze skryptami bash.  %)
No good deed goes unpunished.

krzyszp

Interesujące, czyli moja kobyła zbyt optymalna nie jest :)

A generalnie, to już sobie poradziłem, całość wygląda tak:
#!/bin/bash
cd data
now=$(date +"%T")
echo "Current time : $now"

echo 'przenosze'
for f in *; do mv "$f" "$f.gz"; done
echo 'rozpakowuje'
gunzip *.gz
rm *.gz

echo 'rozpakowane, zaczynam czytac parametry'
now=$(date +"%T")
echo "Current time : $now"

for filename in *_3
do
name=${filename##*/}
        tr '\n' ',' < $filename > plik.txt
name2=${name::-2}
echo $name2 >> plik.txt
        mysql -u root -pXXX --local-infile universe-results -e "LOAD DATA LOCAL INFILE 'plik.txt'  INTO TABLE bhspin2param  FIELDS TERMINATED BY ',' Lines TERMINATED BY '\n'"
rm plik.txt
done
echo 'parametry wczytane, wgrywam dane'
now=$(date +"%T")
echo "Current time : $now"

for filename in *_0
do
name=${filename##*/}
base=${name%1}
name2=${name::-2}
        cat $filename | tr -s ' ' >> "${base}.txt"
awk 'NR % 7 !=0 {printf $0;printf ""} NR % 6 ==0 {print "END"}' ${base}.txt >> ${base}P.txt
tr -d "\n\r" < ${base}P.txt >> ${base}C.txt
sed s/END/\\n/g ${base}C.txt >> ${base}R.txt
sed "s/^/$name2 /" ${base}R.txt > ${base}D.txt
mysql -u root -pXXX --local-infile universe-results -e "LOAD DATA LOCAL INFILE '${base}D.txt'  INTO TABLE bhspin2data  FIELDS TERMINATED BY ' ' LINES TERMINATED BY '\n'"
rm ${base}R.txt
rm ${base}P.txt
rm ${base}.txt
rm ${base}D.txt
rm ${base}C.txt
done
echo 'koniec wgrywania'
now=$(date +"%T")

echo "Current time : $now"

Generalnie, w całości chodzi o 2 pliki - jeden zawierający zmienne startowe dla apki, drugi to właściwy plik z wynikami.
W pierwszym pliku doklejam na końcu jego nazwę i importuję dane do jednej tabeli w mysql, w drugim jest trochę więcej grzebania (oczywiście, dla obu plików doklejam rozszerzenie .gz, bo nie chce mi rozpakowywać bez tej operacji.
W drugim pliku z tego ciągu tworzę plik w formacie csv, w którym każda 6tka linii z danymi to jeden rekord. Na początku każdej linijki dodają nazwę pliku, z którego pochodzą dane.

Generalnie, jak by to się dało zoptymalizować, to byłbym szczęśliwy, bo tych plików naprawdę jest strasznie dużo.


Należę do drużyny BOINC@Poland
Moja wizytówka

apohawk

Jak dla mnie za dużo jest tych plików tymczasowych. Szkoda HDD. Czemu nie w pipe'a? O ile można ogarnąć, co chcesz osiągnąc z tymi plikami :) No chyba, że zapisujesz to na tmpfs, to róbta co chceta.
Rozpakowanie .gz:
gzip -d < spakowany_plik > rozpakowany_plik
albo
gzip -d < spakowany_plik | pipe do obróbki :P

A wydajnościowo to i tak to pewnie klęknie na mysqlu.

Jeśli w nazwach plików źródłowych kiedykolwiek pojawią się białe znaki, to for f in * się rozleci. Ja robię ls| while read f; do ...; done. Jak masz tych plików dużo, to wszelkie * tym bardziej bym odradzał. find albo ls | while read f; do gzip -d < $f > $f.ungzipped; done
Jakby całą obróbkę zamknąć w pętli find albo ls | while read f; do ...; done, to nie trzeba by się martwić o miejsce na dysku na wszystkie rozpakowane pliki jednocześnie.
No good deed goes unpunished.

krzyszp

Białych znaków w nazwie nie ma i nie będzie (to ja generuję te pliki - tzn za pomocą serwera BOINC nazwy są ustalane), więc ten problem nie wystąpi.
Natomiast co do obróbki przez pipe - to po prostu za mało wiem na ten temat, żeby się pokusić (tzn - jak dalej to obrabiać po przesłaniu do pipe).


Należę do drużyny BOINC@Poland
Moja wizytówka

apohawk

Zamiast awk 'NR % 7 !=0 {printf $0;printf ""} NR % 6 ==0 {print "END"}' ${base}.txt >> ${base}P.txt
tr -d "\n\r" < ${base}P.txt >> ${base}C.txt
sed s/END/\\n/g ${base}C.txt >> ${base}R.txt
np.
awk 'NR % 7 !=0 {printf $0;printf ""} NR % 6 ==0 {print "END"}' ${base}.txt | tr -d "\n\r" | sed s/END/\\n/g >> ${base}R.txt
No good deed goes unpunished.