awk :Точное соответствие строки в поле не работает с NUL в качестве разделителя записей

for filename in *.cluster
do
    class=$(cut -d$'\t' -f1)                      # Part 1
    if [ $(wc -l "$filename") -eq 2 ]             # Part 2, start
    then
        class=1
    fi                                            # Part 2, end
    printf '%s\n' "$filename" >> class_"$class".txt # Part 3
done

Он состоит из трех частей:

  1. По умолчанию он классифицирует файл на основе первого поля единственной строки. :переменная classустанавливается на все, что есть в файле, вплоть до первого символа табуляции в каждой строке. Это будет либо 2, либо 3для класса 2 и 3, поскольку эти файлы имеют только одну строку.

    cutразделяет файлы по разделителям ,$'\t'— это способ написания символа табуляции в Bash , а -f1запрашивает cutвывод только первого поля с разделителями.

  2. Если в файле две строки ($(wc -l "$filename") -eq 2), он должен относиться к классу 1, поэтому переменная classпринудительно устанавливается в 1, заменяя ее значение из шага 1. if... fiимеет дело с этим.
  3. Наконец, имя файла добавляется к соответствующему файлу класса:printf '%s\n' "$filename" >> class_"$class".txt

В конце у вас будет три файла class_N.txtдля каждого N в 1, 2, 3, с одним именем файла в строке. Если какой-либо файл имеет другое содержимое, отличное от того, что вы указали в вопросе, например, другое первое поле или длину, вы получите дополнительные файлы классов.

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

2
23.09.2020, 18:34
2 ответа

Ваш файл может содержать поле LF в середине -с окончаниями строки CRLF, например. если он был экспортирован из MS -Excel. В этом случае все, что вам нужно с gawk, это:

awk 'BEGIN{RS=ORS="\r\n"; FPAT="[^,]*|(\"[^\"]*\")+"} $1=="a"' file

Например (использование cat -vтолько для того, чтобы сделать CR видимыми как ^Ms):

$ cat -v file
head1,head2,head3^M
a,b,c^M
b,no a in first field,c^M
a,"with quotes",c^M
a,"with,",c^M
b,a,1^M
a,"with
 newline",c^M
b,1,a^M

$ awk 'BEGIN{RS=ORS="\r\n"; FPAT="[^,]*|(\"[^\"]*\")+"} $1=="a"' file | cat -v
a,b,c^M
a,"with quotes",c^M
a,"with,",c^M
a,"with
 newline",c^M

Если по какой-либо причине описанное выше не работает, см.https://stackoverflow.com/questions/45420535/whats-the-most-robust-way-to-efficiently-parse-csv-using-awkили загрузите/используйте расширение парсера gawks CSV в gawkextlib.

2
18.03.2021, 23:02

смешанный подход sed awk:

$ < file \
sed -e '
  s/$/\x00/
  s/\(,"[^,"]*\)\x00/\1/
  H;1h;$!d;g
  s/\x00\n/\x00/g
' |
awk '/^a,/' RS="\x00" -

Комментарии :смешанный sed+awk Я взял ваш код и немного подправил его, чтобы получить желаемые результаты. Основная идея состоит в том, чтобы убрать новые строки, которые неизменно ставит sed. Поэтому мы воздерживаемся от печати sed после обработки каждой записи. Затем в начале мы удаляем новые строки и передаем эти данные с разделителями NUL в awk с NUL в качестве разделителя записей. Затем мы просто ищем записи, начинающиеся с a,

Выход:

a,b,c
a,"with quotes",c
a,"with,",c
a,"with
 newline",c

Ниже приведены только методы awk и только sed. Они полагаются на двойную цитату внутри поля в кавычках.

чистый подход sed:

$ sed -Ee ':a
    /^(([^"]*"){2})*[^"]*$/!{
      $d;N;ba
    }
    /^a,/!d
' file

чистый awk-подход

$ awk -F\" '
   !(NF%2){
      t = $0; n = NF
      while (getline a > 0) {
        t = t ORS a
        n = n + split(a, _x, FS)
        if (!(nf%2)) break 
      }
      $0 = t
   }/^a,/
' file
0
18.03.2021, 23:02

Теги

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