awk/sed/shell для слияния/объединения данных

Пытаюсь объединить некоторые данные, которые у меня есть. Ввод будет выглядеть так:

foo bar
foo baz boo
abc def
abc ghi

И я хотел бы, чтобы вывод выглядел так:

foo bar baz boo
abc def ghi

У меня есть несколько идей по использованию некоторых массивов в сценарии оболочки, но я искал более элегантное или более быстрое решение.


person Kyle    schedule 30.04.2010    source источник


Ответы (6)


Как насчет присоединиться?

file="file"
join -a1 -a2 <(sort "$file" | sed -n 1~2p) <(sort "$file" | sed -n 2~2p)

Сэды просто разбивают файл на нечетные и четные строки

person pixelbeat    schedule 30.04.2010
comment
Приближается! Мне придется сортировать данные заранее (не работает, если записи с повторяющимися первыми полями не находятся рядом друг с другом), и он не выдает поля, у которых нет дубликатов (опять же, на самом деле это не проблема, это небольшая команда сделала сложную часть моей ситуации). Спасибо! - person Kyle; 01.05.2010
comment
Я уже сделал сортировку для вас. Если вы хотите показать уникальные элементы, то это достигается с помощью -a1 -a2, который я только что обновил в ответе. - person pixelbeat; 01.05.2010
comment
Я все еще получаю дубликаты в своем выводе, если заранее не отсортирую файл. Но, как я уже сказал, это не проблема. И -a1 -a2 исправила мою другую проблему. (Почему я никогда раньше не использовал соединение?!) Еще раз спасибо. - person Kyle; 01.05.2010
comment
Ах да, извините, вам нужно сортировать до, а не после, чтобы нечетное/четное разделение правильно разделяло повторяющиеся поля на отдельные файлы. Ответ обновлен. Также предостережение о соединении заключается в том, что оно работает с парами, поэтому, если у вас есть более 2 определенных ключей, вам нужно будет запустить вышеуказанное в цикле. - person pixelbeat; 01.05.2010

Хотя ответ pixelbeat работает, я не могу сказать, что я в восторге от него. Я думаю, что я бы использовал awk что-то вроде этого:

    { for (i=2; i<=NF; i++) { lines[$1] = lines[$1] " " $i;} }  
END { for (i in lines) printf("%s%s\n", i, lines[i]); }

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

person Jerry Coffin    schedule 01.05.2010
comment
Мне очень нравится это решение, немного более обтекаемое. Спасибо! - person Kyle; 02.05.2010

Нестандартное решение

awk '
    {key=$1; $1=""; x[key] = x[key] $0}
    END {for (key in x) {print key x[key]}}
' filename
person glenn jackman    schedule 01.05.2010

если длина первого поля фиксирована, вы можете использовать uniq с опцией -w. В противном случае вы захотите использовать awk (предупреждение: непроверенный код):

awk '
    BEGIN{last='';}
    {
        if ($1==last) {
            for (i = 1; i < NF;i++) print $i;
        } else {
            print "\n", $0;
            last = $1;
        }
    }'
person soulmerge    schedule 30.04.2010
comment
Длина первого поля будет разной, и я не думаю, что у uniq есть функция, которую я ищу (объединение дополнительных полей в одну строку) - person Kyle; 01.05.2010

Чистый Bash, для действительно чередующихся строк:

infile="paste.dat"

toggle=0
while read -a line ; do
  if [ $toggle -eq 0 ] ; then
    echo -n "${line[@]}"
  else
    unset line[0]               # remove first element
    echo  " ${line[@]}"
  fi
  ((toggle=1-toggle))
done < "$infile"
person Fritz G. Mehner    schedule 01.05.2010

На основе чистого фрагмента Bash от fgm:

text='
foo bar
foo baz boo
abc def
abc ghi
'

count=0
oneline=""
firstword=""
while IFS=" " read -a line ; do
   let count++
   if [[ $count -eq 1 ]]; then
      firstword="${line[0]}"
      oneline="${line[@]}"
   else
      if [[ "$firstword" == "${line[0]}" ]]; then
         unset line[0] # remove first word of line
         oneline="${oneline} ${line[@]}"
      else
         printf "%s\n" "${oneline}"
         oneline="${line[@]}"
         firstword="${line[0]}"
      fi
  fi
done <<< "$text"
person gustaf    schedule 01.05.2010