awk принимает имена файлов вместо файлов в цикле for

Альтернатива Perl:

$ perl -F',' -lane 'if(@F == 1){$prefix = sprintf("%s,",@F);next;};print $prefix . $_' input.csv
John,bim,bam,boom
John,tim,tam,toom
John,lam,loom,lim
Mary,pam,pim,poom
Mary,dam,dim,doom

Это работает при условии, что каждая строка разделена на массив элементов с использованием , в качестве оператора и если в этом массиве только один элемент, мы используем эту строку как префикс и переходим к следующей строке. К другим строкам, длина которых превышает 1 элемент, будет добавлен префикс. Естественно, префикс изменяется тогда и только тогда, когда массив имеет длину 1.

Или короче, используя shift , как предложил Гленн Джекман: ​​

$ perl -F',' -lane 'if(@F == 1){$prefix = shift @F;next;};print $prefix . "," . $_' input.csv       

или

$ perl -F, -lane '$,=","; if (1 == @F) {$name = shift @F} else {print $name, @F}' input.csv
1
21.08.2018, 01:09
2 ответа

При написании сценария оболочки лучше всего сначала указать проверенные переменные, а имена файлов - в последнюю, чтобы вы могли изменять количество указанных файлов. В вашем случае у вас есть номер столбца, файл с шаблонами в нем и два (или, возможно, больше) имени файла для работы. Итак, запустите свой сценарий Bash с

#!/bin/bash
if [ $# -lt 2 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
    echo ""
    echo "Usage: $0 [ -h | --help ]"
    echo "       $0 COLUMN PATTERNFILE [ FILE(s) ... ]"
    echo ""
    exit 0
fi

. Вышеупомянутое предложение if использует форматирование оболочки POSIX старого стиля и будет работать в тире (и других оболочках POSIX), а также в большинстве оболочки старого образца sh . Смысл в том, что если пользователь не указывает аргументы командной строки или просто -h или - help , сценарий просто печатает короткий текст справки.

Вам, кстати, следует расширить текст справки, потому что он значительно упрощает выяснение того, что он делает через два или три месяца, после того как вы забыли, что написали его. (Это случается со мной постоянно, и у меня есть много таких скриптлетов, поэтому я обнаружил, что эта практика стоит небольших усилий.)

Затем извлеките необходимые параметры (только один, выше ) и сдвиньте их, чтобы мы могли использовать "$ @" для ссылки на все имена файлов, указанные в командной строке:

column=$1
patternfile="$2"
shift 2

Обратите внимание, что я предпочитаю использовать двойной цитирует то, что я хочу раскрыть в оболочке, даже если в этом нет явной необходимости. Это связано с тем, что большинство реальных проблем, с которыми я сталкиваюсь со сценариями оболочки, возникают из-за того, что забывает цитировать расширение, когда это было бы необходимо.Эту практику легко запомнить, и, если не считать какого-то всезнающего комментария, что «вам на самом деле не нужны эти двойные кавычки» раздражающим гнусавым тоном, они не причинят вреда.

Давайте затем используем awk для обработки входных файлов:

awk -v column=$column \
  'BEGIN {
       RS = "[\t\v\f ]*(\r\n|\n\r|\r|\n|)[\t\v\f ]*"
       FS = "[\t\v\f ]*;[\t\v\f ]*"
   }

Обратная косая черта в конце первой строки выше просто сообщает оболочке, что команда продолжается на следующей строке. Также обратите внимание, что здесь нет закрывающей одинарной кавычки ', поэтому приведенные ниже строки фактически являются продолжением параметра строки командной строки, который мы передаем для awk .

Правило BEGIN в awk выполняется до обработки файлов. Вышеупомянутый RS устанавливает разделитель записей в соответствии с любым соглашением о новой строке и включает любые начальные или конечные пробелы в каждой строке. Точно так же разделителем полей является точка с запятой, но включая любые пробелы, окружающие ее. Таким образом, a; b имеет два поля, первое из которых a и второе b , ни одно из которых не имеет пробелов.

Я использую следующую идиому, чтобы отслеживать, какой входной файл обрабатывается:

    FNR==1 { ++filenum }

Если это просто означает, что для самой первой записи в каждом входном файле, который мы обрабатываем, мы увеличиваем переменную filenum . Увеличение неинициализированной переменной аналогично увеличению нуля, поэтому мы получаем 1 для первого входного файла и так далее.

Мы просто хотим запомнить содержимое каждой строки в первом входном файле, нашем файле шаблонов:

    filenum==1 { pattern[$0] }

Массивы AWK ассоциативны, поэтому мы можем просто использовать ассоциативный массив для хранения известных шаблонов.Выше мы использовали забавную функцию awk в наших интересах: если вы попытаетесь получить доступ к записи ассоциативного массива, которая еще не существует, awk создаст ее!

Для остальных файлов мы просто проверяем, соответствует ли (точно) поле $ column (предоставленное скриптлету awk в awk-переменной column ) любому из обнаруженных шаблонов. в первом файле, и если это так, мы печатаем всю запись:

    filenum > 1 && ($column in pattern) { printf "%s\n", $0 }

Выше, $ column имеет другое значение по сравнению со сценарием оболочки. Здесь столбец является переменной, а $ column расширяется до значения столбца -го поля в текущей записи (нулевой столбец - вся запись, тем не мение). Синтаксис foo в массиве - это awkism для проверки того, содержит ли массив ключ foo . Итак, в целом, для второго и последующих входных файлов, если значение поля столбца было указано в первом входном файле, запись печатается. на стандартный вывод.

Мы все еще находимся в строке параметра командной строки awk , и нам нужно закрыть строку в одинарных кавычках. Мы также хотим снабдить его именами файлов:

    ' "$patternfile" "$@"

, которые завершают этот скриптлет awk.

2
27.01.2020, 23:34

Если вы просто хотите взять список шаблонов и набор файлов и распечатать имена всех файлов, которые соответствуют каждому шаблону в определенном столбце, все, что вам нужно, это GNU awk (по умолчанию в Linux):

awk -F';' '{
                if(NR==FNR){ 
                    p[$0]++; 
                    next
                } 
                if($3 in p){
                    printf "%s found in %s\n", $3,FILENAME; 
                    nextfile
                }
            }' patterns file1.csv file2.csv fileN.csv

Объяснение

  • awk -F ';' : установите разделитель полей на ; .
  • if (NR == FNR) {p [$ 0] ++; next} : NR - номер текущей строки ввода, а FNR - строка текущего файла количество. Они равны только тогда, когда обрабатывается первый файл. Таким образом, это сохранит каждую строку файла шаблонов (1-й файл) в массиве p и перейдет к следующей строке. Он будет запущен только для файла шаблонов.
  • if ($ 3 в p) {printf "% s найдено в% s \ n", $ 3, FILENAME; nextfile : Теперь мы смотрим на файлы csv. если третье поле является одним из элементов массива p (если оно было в файле шаблонов), выведите третье поле (шаблон) и имя файла, в котором он был найден. Затем перейдите к следующий файл. Переменная FILENAME содержит путь к текущему файлу, который обрабатывается. nextfile является функцией gawk и делает то, что написано на банке: он переходит к следующему файлу для обработки.

Например, для этих файлов:

$ cat patterns 
foo
bar
baz

$ cat file1.csv 
blah;blah;foo;blah
blah;blah;foo;blah
blah;blah;foo;blah

$ cat file2.csv 
blah;blah;bar;blah

$ cat file3.csv 
blah;blah;baz;blah

Вы получите следующий результат:

$ awk -F';' '{if(NR==FNR){p[$0]++; next} if($3 in p){printf "%s found in %s\n", $3,FILENAME; nextfile}}' patterns file*csv 
foo found in file1.csv
bar found in file2.csv
baz found in file3.csv

Если вы можете иметь каждый шаблон в нескольких файлах, вы можете использовать немного другой подход:

awk -F';' '{
            if(NR==FNR){ 
                p[$0]++; 
                next
            } 
            if($3 in p && !seen[FILENAME][$3]){
                printf "%s found in %s\n", $3,FILENAME; 
                seen[FILENAME][$3]++
            }
        }' patterns file1.csv file2.csv fileN.csv

На этот раз нет nextfile , поскольку нам нужно обработать весь файл, и есть счетчик, который увеличивается каждый раз, когда в данном файле обнаруживается шаблон, поэтому мы не сообщаем один и тот же шаблон несколько раз.

Итак, изменив file1.csv ​​выше на:

$ cat file1.csv 
blah;blah;foo;blah
blah;blah;baz;blah
blah;blah;bar;blah
blah;blah;foo;blah

Мы получим:

$ awk -F';' '{if(NR==FNR){p[$0]++; next} if($3 in p && !seen[FILENAME][$3]){printf "%s found in %s\n", $3,FILENAME; seen[FILENAME][$3]++}}' patterns file*csv 
foo found in file1.csv
baz found in file1.csv
bar found in file1.csv
bar found in file2.csv
baz found in file3.csv

Если это слишком медленно, как это может быть для больших файлов, вы можете изменить его, чтобы он останавливался чтение файла, если в нем уже найдены все шаблоны:

awk -F';' '{
            if(NR==FNR){ 
                p[$0]++; 
                next
            } 
            if($3 in p && !seen[FILENAME][$3]){
                printf "%s found in %s\n", $3,FILENAME; 
                seen[FILENAME][$3]++
            }
            if( length(seen[FILENAME]) == length(p) ){
                nextfile
            }
           }' patterns file1.csv file2.csv fileN.csv
0
27.01.2020, 23:34

Теги

Похожие вопросы