Объединить два файла по столбцу и добавить 0, если запись отсутствует

В большинстве случаев будет много проблем из-за того, как работает буферизация stdio. Обходной путь для Linux может состоять в том, чтобы использовать программу stdbufи запустить команду с помощью coproc, чтобы вы могли явно управлять чередованием вывода.

Далее предполагается, что команда будет выводить одну строку после каждой строки ввода.

#!/bin/bash
coproc stdbuf -i0 -o0 "$@"
IFS=
while read -r in ; do
    printf "%s " "$in"
    printf "%s\n" "$in" >&${COPROC[1]}
    read -r out <&${COPROC[0]}
    printf "%s\n" "$out"
done

Если требуется более общее решение, так как OP требует только каждой строки ввода в программу, чтобы в конечном итоге вывести одну строку, а не немедленно, тогда необходим более сложный подход. Создайте цикл событий, используя read -t 0, чтобы попытаться прочитать из стандартного ввода и процесса co -, если у вас есть один и тот же «номер строки» для обоих, затем выведите, в противном случае сохраните строку. Чтобы избежать использования 100% процессора, если в любом раунде цикла событий ни один из них не был готов, введите небольшую задержку перед повторным запуском цикла событий. Существуют дополнительные сложности, если процесс выводит неполные строки, их необходимо буферизовать.

Если необходимо это более общее решение, я бы написал его, используя expect , так как оно уже имеет хорошую поддержку обработки сопоставления шаблонов для нескольких входных потоков. Однако это не решение для bash/zsh.

0
03.12.2021, 14:11
4 ответа

Я не знаю ни одной команды, которая могла бы это сделать, но это можно написать в сценарии:

while IFS='' read -r l1; do
  grep "^${l1}" File2 || echo -e "${l1}\t0"
done < <(cat File1)
0
03.12.2021, 14:39

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

awk 'BEGIN{FS=OFS="\t"}
     !mainfile{val[$1]=$2;next}
     {if ($1 in val) {$2=val[$1]} else {$2=0}} 1' File2 mainfile=1 File1

Это работает следующим образом:

  • Прежде чем что-либо обрабатывать, мы устанавливаем разделитель полей ввода и вывода на TAB.
  • При обработке первого входного файла(File2в данном случае ), указываем неинициализированной переменной mainfile, мы просто записываем «значение» для каждого из «плодов» в ассоциативный массив val. После этого мы сразу пропускаем обработку до следующей строки ввода (и пропускаем ту часть, которая должна применяться только при обработке File1).
  • Перед обработкой следующего файла awkсначала оценит оператор mainfile=1, чтобы установить mainfileв 1.
  • После установки mainfileпервое правило игнорируется, и мы обрабатываем только второе правило. Здесь мы проверяем, есть ли «значение», сопоставленное с записью в столбце 1. Если да, мы используем это значение для столбца 2, в противном случае мы устанавливаем для столбца 2 значение 0.
  • Кажущееся отклонение 1за пределами блоков правил предписывает awkраспечатать текущую строку, включая любые сделанные модификации.
0
03.12.2021, 14:46
$ join -a 1 -e 0 -o 0,2.2 <(sort File1) <(sort File2)
banana 2
berry 0
cherry 1
orange 1
strawberry 0

Это использует joinдля выполнения реляционной операции JOIN между файлами. Это требует сортировки обоих файлов, поэтому мы сортируем их в процессе замены каждый (вы, очевидно, можете предварительно -отсортировать данные, если хотите ). Команда выведет список всех строк из первого входного файла(-a 1)и заменит отсутствующие поля на0(-e 0). Полями в выводе будут поле соединения (первое поле в каждом файле по умолчанию, и записанное 0в аргументе опции -o)и второе поле из второго файла(2.2).

Pro :Быстро (, особенно если данные уже отсортированы )и память -эффективна.
Con :Re -упорядочивает данные.


Чтобы сохранить исходный порядок File1, вы можете использовать awkвместо:

$ awk 'NR == FNR { key[$1] = $2; next } { $2 = ($1 in key) ? key[$1] : 0 }; 1' File2 File1
orange 1
banana 2
berry 0
cherry 1
strawberry 0

Это считывает 1-й столбец File2как ключи в ассоциативном массиве key, а 2-й столбец как связанные с ними значения.

При чтении File1(NRуже не равно FNR), устанавливаем 2-му столбцу либо значение из массива key, если есть ключ, соответствующий 1-му столбцу, либо на 0, если такого ключа нет.

Вы можете несколько сократить код, злоупотребив тем фактом, что неинициализированное значение равно нулю в арифметическом контексте:

$ awk 'NR == FNR { key[$1] = $2; next } { $2 = 0+key[$1] }; 1' File2 File1
orange 1
banana 2
berry 0
cherry 1
strawberry 0

Pro :Выход заказывается в соответствии с File1.
Con :Данные из File2хранятся в памяти (только при чтении большого количества строк ).

4
03.12.2021, 14:49

Похоже на то, что вы можете очень быстро собрать на питоне

#!/usr/bin/env python3
"""
Overly grande solution to the problem posed in the question.
"""
import sys

# Check whether the user specified two arguments to the script
if not len(sys.argv) == 3:
  sys.stderr.write(f"Usage: {sys.argv[0]} File1 File2")
  sys.exit(-1)

# Empty dictionary
dictionary = {}

# Fill the dictionary with zero-entries from the first file
with open(sys.argv[1]) as file:
  for line in file:
   dictionary[line.strip()] = 0

# Replace the zero entries when found in second file
with open(sys.argv[2]) as file:
  for line in file:
    entry, value = line.split()
    dictionary[entry] = int(value)

# print the result table
print("Output\tValue")
for key, value in dictionary.items():
  # We're using the format string syntax to give the count
  # field a constant length of 8, so that things are nicely
  # right-aligned.
  print(f"{key}\t{value:8}")
0
03.12.2021, 15:03

Теги

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