Форум русскоязычного сообщества Ubuntu


Следите за новостями русскоязычного сообщества Ubuntu в Twitter-ленте @ubuntu_ru_loco

Автор Тема: Посчитать определённое поле для всех строк в файлах:Как это сделать на Bash?  (Прочитано 2048 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн ubuntar

  • Автор темы
  • Участник
  • *
  • Сообщений: 129
  • Qui quaerit, reperit
    • Просмотр профиля
Есть много файлов типа:
файл 1:
2  | data1 | data2 | 0 | 200 | 0   | 0  | 11
30 | data3 | data4 | 0 | 656 | 676 | 12 | 0
файл 2:
1  | data5 | data6 | 2 | 45  | 86  | 0  | 0
2  | data1 | data2 | 0 | 343 | 0   | 0  | 11
30 | data3 | data4 | 0 | 565 | 676 | 12 | 0

и необходимо посчитать для всех строк с одинаковым первым полем (например 30) сумму всех чисел в поле номер 5 для данных строк.
например, для строк начинающихся на 30 это будет сумма 656 + 565 = 1221. А для номера 2 сумма соответственно 343 + 200 = 543. После чего нужно вывести строку с максимальной суммой в формате на примере строки 30: "30 data3 data4 1221".
 Можно использовать только команды: printf, echo, wc, tail, head, grep, sort, uniq, cut, tee, tr, read, временные файлы создавать нельзя.
Я битый час делал этот скрипт, но он считает неправильно: если на входе файл, то он тупо считает сумму поля для всех строк в файле, а мне надо как описано выше..
 Как это сделать (точнее, что поправить, чтобы работало)?
#! /bin/bash

function findMax {

  for file in $* ; do
    cat $file | tr '|' ' ' | sort -bd | calcSums
  done     
}

function calcSumForSimilar {

  local sum_for_similarc=0
  while read -a line ; do
    let sum_for_similar+=${line[4]}     
  done
  echo $sum_for_similar     
}

function calcSums {

  while read -a line; do
    sum=$(cat "${line[@]}"  | grep ${line[0]} | calcSumForSimilar)
  done
  echo ${line[0]} ${line[1]} ${line[2]} $sum
}

findMax $*
Спасибо!

Оффлайн andrew_bye

  • Почётный модератор
  • Старожил
  • *
  • Сообщений: 2698
    • Просмотр профиля
Скрипт обязательно должен быть на Bash?

Оффлайн ubuntar

  • Автор темы
  • Участник
  • *
  • Сообщений: 129
  • Qui quaerit, reperit
    • Просмотр профиля
Да, только bash, используя printf, echo, wc, tail, head, grep, sort, uniq, cut, tee, tr, read (pipe конечно тоже, только без временных файлов)

Оффлайн andrew_bye

  • Почётный модератор
  • Старожил
  • *
  • Сообщений: 2698
    • Просмотр профиля
Чем вызваны такие ограничения?

Оффлайн ubuntar

  • Автор темы
  • Участник
  • *
  • Сообщений: 129
  • Qui quaerit, reperit
    • Просмотр профиля
Это домашка по bash, и можно использовать только те команды, которые давались на лекциях и практикумах =)
Я вчера над ней всю ночь сидел, ковырялся в манах, на этом скрипте я застрял.. Ошибку понял (почему не работало), а как продвинуться дальше пока не знаю. Надеюсь, знающие люди подскажут.

Оффлайн andrew_bye

  • Почётный модератор
  • Старожил
  • *
  • Сообщений: 2698
    • Просмотр профиля
Это плохо, поскольку для решения поставленной задачи я бы писал прогу на Perl, а не на Bash.

Оффлайн absent

  • Активист
  • *
  • Сообщений: 368
  • Ubuntu 16.04
    • Просмотр профиля
надеюсь, cat можно использовать (он не перечислен в допустимых)?
d=30
f=$(cat files/file* | grep "^$d[^0-9]")
n=$(echo "$f" | cut -d "|" -f 5)
res=0
for i in $n; do
  let res=$res+$i
done
printf "%d %s %s %d\n" $(echo "$f" | head -n1 | cut -d "|" -f 1-3 | tr -d "|") $res
в папке files лежат файлы file1 file2 file3... с нужными таблицами
в d задаём нужное число

Оффлайн ubuntar

  • Автор темы
  • Участник
  • *
  • Сообщений: 129
  • Qui quaerit, reperit
    • Просмотр профиля
надеюсь, cat можно использовать (он не перечислен в допустимых)?
Да, cat можно, я забыл перечислить.
в d задаём нужное число
Спасибо за идею. А можно как-то сделать, не задавая d вручную?
В том-то и соль задачи, что я не знаю, каким будет первое поле (скажем, назовём id), но для всех строк из разных файлов, у которых оно одинаковое, нужно посчитать сумму n-го поля, а потом напечатать строку с максимальной суммой.
Я понимаю, как пройтись по всем файлам, распечатать их в поток и сортировать этот поток по id, но как сделать переход от сортированного общего потока к счету сумм для каждого одинакового id, вот тут я застрял :)
Хотя бы узнать, как посчитать все эти суммы и вывести строку с суммой в конце (все строки с одинаковым id отличаются только полем, которое нужно посчитать, проверку файлов на валидность делают другие скрипты), дальше я бы перенаправил всё это в pipe и там уже проще всё.
« Последнее редактирование: 28 Мая 2013, 18:52:25 от ubuntar »

Оффлайн absent

  • Активист
  • *
  • Сообщений: 368
  • Ubuntu 16.04
    • Просмотр профиля
я думал, что только по одной переменной считается. невнимательно прочел условие. для подсчета всех вхождений id (это всегда число?) скрипт можно дополнить еще одним циклом.
f=$(cat files/file*)
d=$(echo "$f" | sort -sunk1 | cut -d "|" -f 1)
for i in $d; do
  n=$(echo "$f" | grep "^$i[^0-9]")
  m=$(echo "$n" | cut -d "|" -f 5)
  res=0
  for j in $m; do
    let res=res+j
  done
  echo "$(echo "$n" | head -n1 | cut -d "|" -f 1-3 | tr -d "|") $res"
done
вообще тут ничего сложного, смотрите, модифицируйте как нужно.

Оффлайн ubuntar

  • Автор темы
  • Участник
  • *
  • Сообщений: 129
  • Qui quaerit, reperit
    • Просмотр профиля
это всегда число?
Да, это число, валидность данных уже гарантирована на момент подсчёта.

Огромное спасибо, очень помогли! Сегодня ночью сяду и буду пилить дальше =)

Оффлайн victor00000

  • Старожил
  • *
  • Сообщений: 15568
  • Глухонемой (Deaf)
    • Просмотр профиля
Это плохо, поскольку для решения поставленной задачи я бы писал прогу на Perl, а не на Bash.
спс))
Wars ~.o

Оффлайн ArcFi

  • Старожил
  • *
  • Сообщений: 15189
    • Просмотр профиля
    • aetera.net
Код: (bash) [Выделить]
sort [файлики] | while read
    do
        [выковыриваем первый столбец]
        if [то же число]
        then
            [добавляем к сумме]
        else
            [выводим и обнуляем сумму]
        fi
    done

Оффлайн ubuntar

  • Автор темы
  • Участник
  • *
  • Сообщений: 129
  • Qui quaerit, reperit
    • Просмотр профиля
Сегодня, наконец, после долгих переделываний скрипта, добился правильной работы: теперь скрипт работает именно как задумано (хотелось всё же самому сделать, хотя поковырял и ваши варианты, раз эдак 200). Фуух :)
Получилось коряво и больше в стиле С, но зато моё)
И вот вопрос: можно ли как-то укоротить код, чтобы делал ровно то же самое? У меня там явное дублирование кода (там где echo), да и вообще хочется покрасивей поглядеть вариант.
Код получился такой:
Код: (bash) [Выделить]
#! /bin/bash

field_to_calc=6

function concat_files {

  if [[ $# -le 0 ]] ; then
    echo "Missing argument"
    exit 1
  fi
  for file in $* ; do
    cat $file
  done     
}

function calcSums {
  read -a first
  local i=${first[0]}
  local s=${first[$field_to_calc]}
  local str="${first[0]} ${first[1]} ${first[2]}"
  while read -a line; do
    if  [[ ${line[0]} -eq $i ]] ; then
      ((s+=${line[$field_to_calc]}))
    else
      echo -e $str $s
      i=${line[0]} 
      s=${line[$field_to_calc]}
    fi
    str="${line[0]} ${line[1]} ${line[2]}"
  done
  echo -e $str $s
}

concat_files $* | tr '|' ' ' | sort -bsnk1 | calcSums | sort -rbsnk4 | head -n 1
Заранее благодарен за советы.
« Последнее редактирование: 31 Мая 2013, 22:08:02 от ubuntar »

Оффлайн Vitsliputsli

  • Старожил
  • *
  • Сообщений: 1293
    • Просмотр профиля
Не слишком красиво, но как-то так можно:
read -p "?" i; grep -h "^$i |" * | (while read f; do hd=`cut -d'|' -f-4 <<< $f`; tl=`cut -d'|' -f6- <<< $f`; sum=$[sum+`cut -d'|' -f5 <<<$f`]; done; echo $hd'| '$sum' |'$tl)

Оффлайн ubuntar

  • Автор темы
  • Участник
  • *
  • Сообщений: 129
  • Qui quaerit, reperit
    • Просмотр профиля
Не слишком красиво, но как-то так можно:
read -p "?" i; grep -h "^$i |" * | (while read f; do hd=`cut -d'|' -f-4 <<< $f`; tl=`cut -d'|' -f6- <<< $f`; sum=$[sum+`cut -d'|' -f5 <<<$f`]; done; echo $hd'| '$sum' |'$tl)
А что такое "<<<" ? Я пока встречал только один или два символа, три не видел.

 

Страница сгенерирована за 0.079 секунд. Запросов: 25.