У меня есть некоторый опыт работы с терминалами Inix, полученный во время научных стажировок, в которых я принимал участие. В основном я использовал несколько утилит, таких как grep
, awk
, и sed
, но есть одна вещь, которую я уже некоторое время пытаюсь понять, и которая действительно позволила бы мне гораздо эффективнее справляться с обработкой чисел, которую мне приходится выполнять.
У меня есть скрипт run.awk
, который выполняет некоторые манипуляции с большой коллекцией массивных текстовых файлов. Как есть, он возьмет файл chloride.out
, извлечет из него данные и запишет chloride.cm
.
Можно ли как-то заставить этот скрипт принимать *.out
и записывать *.cm
файлы на основе начальной подстановочной фразы в оболочке?
Количество написанных мной скриптов для обработки больших объемов данных, которые мне приходилось выполнять более сотни итераций, просто раздражает.
В идеале я хотел бы узнать, есть ли способ сделать это для всех моих скриптов с чем-то через оболочку. Если это не может быть автоматизировано в оболочке или эквиваленте, могу ли я хотя бы автоматизировать свои awk
скрипты подобным образом, как я описал?
решение1
Вы, безусловно, можете заставить awk работать с несколькими файлами с помощью подстановочных знаков. Одним из предложений было бы оставить run.awk
как общую "функцию", которая принимает один файл и создает один выходной файл, а затем вызвать ее из другого скрипта, который затем мог бы позаботиться об ассимиляции входных и выходных файлов.
Пример
Это будет скрипт Bash, мы можем назвать его awk_runner.bash
.
#!/bin/bash
for ifname in *.out; do
ofname=${ifname/.out/.cm}
printf "IN: %s, OUT: %s\n" $ifname $ofname
printf "running run.awk with %s & %s\n\n" $ifname $ofname
run.awk $ifname $ofname
done
Образец запуска
Я создал пример каталога с несколькими тестовыми файлами.
$ touch file{1..4}.out
В результате было создано 4 файла:
$ ls -1
file1.out
file2.out
file3.out
file4.out
Теперь запускаем наш скрипт:
$ ./awk_runner.bash
IN: file1.out, OUT: file1.cm
running run.awk with file1.out & file1.cm
IN: file2.out, OUT: file2.cm
running run.awk with file2.out & file2.cm
IN: file3.out, OUT: file3.cm
running run.awk with file3.out & file3.cm
IN: file4.out, OUT: file4.cm
running run.awk with file4.out & file4.cm
После каждой строки, начинающейся с «running...», наш скрипт может быть запущен отсюда.
Файлы в списке
Допустим, вместо использования подстановочного знака *.out
у нас есть файл со списком имен файлов, например:
$ cat filelist.txt
file1.out
file2.out
file3.out
file4.out
Мы могли бы использовать эту измененную версию нашего скрипта, которая использовала бы while
цикл вместо for
цикла. Теперь давайте назовем этот вариант скрипта, awk_file_runner.bash
:
#!/bin/bash
while read ifname; do
ofname=${ifname/.out/.cm}
printf "IN: %s, OUT: %s\n" $ifname $ofname
printf "running run.awk with %s & %s\n\n" $ifname $ofname
run.awk $ifname $ofname
done < filelist.txt
Эта версия скрипта считывает входные данные из файла filelist.txt
:
done < filelist.txt
Затем для каждого витка цикла while
мы используем read
команду для считывания строки из входного файла.
while read ifname; do
Затем он выполняет все действия так же, как и первый скрипт, запуская скрипт awk
по run.awk
мере прохождения каждой строки файла.
решение2
Вместо того, чтобы писать оболочку оболочки и порождать новый экземпляр awk для каждого обрабатываемого файла, вы можете сделать это в awk напрямую. Если у вас уже есть скрипт awk, вы можете получить доступ к текущему файлу с помощью переменной FILENAME. Таким образом, если вы запустите awk 'some commands' file1 file2
, вы сможете определить, работаете ли вы с file1 или file2, с помощью FILENAME. Вы также можете использовать >
on print
/ printf
in awk. Таким образом, если у вас есть скрипт awk вроде
/pattern/{ print $1,$3 }
вы могли бы легко сделать
/pattern/{ print $1,$3 > FILENAME".processed" }
или использовать FNR=1
, чтобы определить, когда вы находитесь в новом файле, и создать переменную для выполнения более сложных манипуляций с именем файла. Например, заменить расширение .in
на .out
, как в
sauer@humpy:/tmp$ grep . file*.in
file1.in:a
file1.in:b
file2.in:c
sauer@humpy:/tmp$ awk 'FNR=1{out=FILENAME;sub("\.in$",".out",out)} {print "processed"$0 > out}' file*.in
sauer@humpy:/tmp$ grep . file*.out
file1.out:processeda
file1.out:processedb
file2.out:processedc
Я использую grep .
для отображения имени файла и содержимого нескольких файлов здесь, что также является забавным трюком. Но важно установить значение переменной out
в измененную версию, FILENAME
когда FNR
изменяется на 1 (так что мы находимся на строке 1 файла), а затем перенаправить все отпечатки в out
. Обратите внимание, что это немного опасно, поскольку несоответствие расширения приведет к отсутствию замены, что приведет к перезаписи ваших входных файлов. Поэтому было бы неплохо добавить отказоустойчивую проверку, чтобы убедиться в этом out != FILENAME
или что-то в этом роде. Это оставлено в качестве упражнения для читателя. ;)
Если вам нужен файл, содержащий список имен файлов, проще всего запустить его так:
awkscript $(< /path/to/filename_list_file )
Который берет содержимое filename_list_file
и помещает его в командную строку.