У меня есть большой файл input.dat, который смотрит как показано ниже.
kpoint1 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
kpoint2 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
Я должен Разделить файл на 2 меньших как ниже
kpoint1.dat
:
kpoint1 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
и kpoint2.dat
:
kpoint1 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
Я записал маленький сценарий, чтобы сделать это. Сценарий показывают ниже.
for j in {1..2}
do
awk '$1=="kpoint'$j'" {for(i=1; i<=3; i++){getline; print}}' tmp7 >kpoint'$j'.dat
done
Сценарий создает выходные файлы с желаемыми именами. Но все файлы пусты. Кто-либо может помочь мне решить это?
Это может быть сделано полностью в awk
:
$ awk '$1 ~ /kpoint[0-9]/ { file = $1 ".dat" } {print > file}' file
$ head kpoint*
==> kpoint1.dat <==
kpoint1 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
==> kpoint2.dat <==
kpoint2 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
Awk также поддерживает > file
для перенаправления с некоторыми тонкими различиями (см. руководство awk's GNU для больше).
В то время как ответ muru является самым простым, существует несколько других путей без использования awk.
Подход с awk состоит в основном в том, что мы пишем в определенное имя файла и изменяем то имя файла, если и только если мы встречаемся с kpoint в начале строки. Тот же подход может быть сделан с Perl:
$ perl -ane '$p=$F[0] if $F[0] =~ /kpoint/;open($f,">>",$p . ".dat"); print $f $_' input.txt
Вот то, как это работает:
-a
флаг позволяет нам использовать специальное предложение @F
массив слов, которые были автоматически разделены от каждой строки входного файла. Таким образом $F[0]
относится к первому слову, точно так же, как $1
в awk$p=$F[0] if $F[0] =~ /kpoint/
предназначен для изменения $p
(который предназначен, чтобы быть переменной префикса), если и только если kpoint
находится в строке. Улучшение того соответствия шаблона могло быть /^ *kpoint/
при каждом повторении мы открываемся для добавления файла, который имеет имя $p
присоединенный с .dat
строка; обратите внимание, что добавление части важно. Если Вы хотите иметь четкое выполнение, Вы, вероятно, хотите избавиться от старых kpoint
файлы. Если мы хотим, чтобы файл всегда был создан новый и перезаписан, то мы можем reqrite исходная команда как:
$ perl -ane 'if ($F[0] =~ /kpoint/){$p=$F[0]; open($f,">",$p . ".dat")}; print $f $_' input.txt
print $f $_
просто печать к любому имени файла мы имеем открытый.От Вашего примера кажется, что каждая запись состоит из 5 строк. Если это постоянно, мы можем разделить файл тот путь, не полагаясь на сопоставление с образцом с split
. Конкретно эта команда:
$ split --additional-suffix=".dat" --numeric-suffixes=1 -l 5 input.txt kpoint
В этой команде опции следующие:
--additional-suffix=".dat"
помехи .dat
суффикс, который будет добавлен к каждому созданному файлу--numeric-suffixes=1
позволит нам добавление, изменяющее числа, запускающиеся в 1 к каждому имени файла-l 5
позволит разделять входной файл каждые 5 строкinput.txt
файл, который мы пытаемся разделитьkpoint
будет статический префикс имени файла И здесь как это работает на практике:
$ split --additional-suffix=".dat" --numeric-suffixes=1 -l 5 input.txt kpoint
$ cat kpoint01.dat
kpoint1 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
$ cat kpoint02.dat
kpoint2 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
Дополнительно, мы могли также добавить --suffix-length=1
сохранять длину каждого числового суффикса короче как kpoint1
вместо kpoint01
, но это могло бы быть проблемой, если у Вас есть большое количество kpoint
s.
Этот подобен ответу muru, кроме здесь мы используем другое соответствие шаблона, а также другой подход создания переменной имени файла через sprintf()
$ awk '/^\ *kpoint/{f=sprintf("%s.dat",$1)};{print > f}' input.txt
В то время как awk
и split
подходы короче, другие инструменты, такие как Python хорошо подходят для обработки текста, и мы можем использовать их для реализации большего количества подробных, но рабочих решений.
Сценарий ниже делает точно, что, и он воздействует на идею изучить назад список строк, которые мы сохраняем. Сценарий продолжает сохранять строки, пока он не встречается kpoint
в начале строки, что означает, что мы достигли новой записи, и который также означает, что мы должны записать предыдущую запись в ее соответствующий файл.
#!/usr/bin/env python3
import sys
def write_entry(pref,line_list):
# this function writes the actual file for each entry
with open(".".join([pref,"dat"]),"w") as entry_file:
entry_file.write("".join(line_list))
def main():
prefix = ""
old_prefix = ""
entry=[]
with open(sys.argv[1]) as fd:
for line in fd:
# if we encounter kpoint string, that's a signal
# that we need to write out the list of things
if line.strip().startswith('kpoint'):
prefix=line.strip().split()[0]
# This if statement counters special case
# when we just started reading the file
if not old_prefix:
old_prefix = prefix
entry.append(line)
continue
write_entry(old_prefix,entry)
old_prefix = prefix
entry=[]
# Keep storing lines. This works nicely after old
# entry has been cleared out.
entry.append(line)
# since we're looking backwards, we need one last call
# to write last entry when input file has been closed
write_entry(old_prefix,entry)
if __name__ == '__main__': main()
Почти та же идея как подход Perl - мы продолжаем писать все в определенное имя файла и изменяем имя файла только, когда мы находим строку с kpoint
в нем.
#!/usr/bin/env bash
while IFS= read -r line;
do
case "$line" in
# We found next entry. Use word-splitting to get
# filename into fname variable, and truncate that filename
*kpoint[0-9]*) read fname trash <<< $line &&
echo "$line" > "$fname".dat ;;
# That's just a line within entry. Append to
# current working file
*) echo "$line" >> "$fname".dat ;;
esac
done < "$1"
# Just in case there are trailing lines that weren't processed
# in while loop, append them to last filename
[ -n "$line" ] && echo "$line" >> "$fname".dat ;