Unix резка bash и grep

У меня есть текстовый файл с именем db.txt. Некоторые примеры строк из файла выглядят так:

Гарри Поттер и Философский камень: Дж.К. Роулинг: 21,95:100:200

Гарри Поттер и Тайная комната: Дж.К. Роулинг: 21,95:150:300

Властелин колец, Братство кольца: J.R.R. Толкин:32.00:500:500

Игра престолов: Джордж Р. Р. Мартин: 44,50: 300: 250

Затем в моем сценарии у меня есть следующие строки:

echo "Enter title:"
read TITLE

cut -d ":" -f 1 db.txt | grep -iw "$TITLE" | while read LINE
do
    STRING="`echo $LINE | cut -d ":" -f 1`,"
    STRING="$STRING `echo $LINE | cut -d ":" -f 2`, "
    STRING=" \$$STRING`echo $LINE | cut -d ":" -f 3`,"
    STRING=" $STRING`echo $LINE | cut -d ":" -f 4`,"
    STRING=" $STRING`echo $LINE | cut -d ":" -f 5`"
done

Есть ли способ получить определенное поле из вырезания, а затем передать всю строку в цикл while?

Например, если я ввел Гарри Поттера, он должен отображать:

Гарри Поттер и Философский камень, Дж.К. Роулинг, 21,95, 100, 200 долларов.

Гарри Поттер и Тайная комната, Дж.К. Роулинг, 21,95, 150, 300 долларов.


person Jary Rym    schedule 09.01.2013    source источник
comment
Цикл на строках в оболочке — это нормально. Все команды (grep, sed, cut и т.д.) уже используют цикл на строках.   -  person Zulu    schedule 09.01.2013


Ответы (6)


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

Идея заключалась бы в том, чтобы прочитать файл построчно, а затем разбить строку на массив. Как только вы это сделаете, сделайте сравнения и выведите желаемый результат.

Вот демонстрация техники:

#! /bin/bash
echo "Title:"
read title

# shopt -s nocasematch           # if you want case-insensitive matching

while read line ; do             # this read takes data from input.txt, see
                                 # end of loop
        IFS=: read -a parts <<< "$line"  # this splits the line on ":" into
                                         # an array called parts

        if [[ ${parts[0]} =~ $title ]] ; then  # regex matching
                printf "%s -- %s\n" "${parts[1]}" "${parts[2]}"
        fi
done < input.txt
person Mat    schedule 09.01.2013
comment
Еще одну вещь, которую я хочу добавить, как вы проверяете нечувствительность к регистру и как мне распечатать числа сзади? Я пробовал %d, но числа заканчиваются нулем. - person Jary Rym; 09.01.2013
comment
Раскомментируйте строку shopt в сообщении, чтобы получить соответствие без учета регистра. Попробуйте $%.02f для суммы в долларах, %d для целых чисел отлично работает с предоставленными вами данными. - person Mat; 09.01.2013
comment
grep Harry Potter db.txt должен сделать это! - person Arif Burhan; 28.02.2016
comment
@ArifBurhan: не даст желаемого результата и не будет соответствовать только названию - person Mat; 28.02.2016

Следующим шагом от grep и cut будет awk. Если вы не должны делать это с помощью bash (это домашнее задание?), то awk значительно упростит задачу:

awk -F: '/harry potter/ { sub(/^/,"$",$(NF-2)); print }' IGNORECASE=1 OFS=", " db.txt

Тестовый ввод:

Harry Potter and the Sorcerer's Stone:J.K. Rowling:21.95:100:200
Harry Potter and the Chamber of Secrets:J.K. Rowling:21.95:150:300
Lord of the Rings, The Fellowship of the Ring:J.R.R. Tolkien:32.00:500:500
A Game of Thrones:George R.R. Martin:44.50:300:250

Тестовый вывод:

Harry Potter and the Sorcerer's Stone, J.K. Rowling, $21.95, 100, 200
Harry Potter and the Chamber of Secrets, J.K. Rowling, $21.95, 150, 300
person Steve    schedule 09.01.2013

read -p "Enter title: " TITLE
while IFS=: read title author price x y; do
    if [[ ${title,,} == *${TITLE,,}* ]]; then
        printf "%s, %s, $%s, %s, %s\n" "$title" "$author" "$price" "$x" "$y"
    fi
done < db.txt

Тест в команде if выполняет простое сопоставление с glob, но без учета регистра, поэтому он будет совпадать, если пользователь вводит «potter».

Или используйте sed для изменения разделителей:

read -p "Enter title: " TITLE
sed '/'"$TITLE"'/I!d; s/:/, /g' db.txt

что означает удалить все строки, которые не соответствуют НАЗВАНИЕ, а затем преобразовать разделитель.

person glenn jackman    schedule 09.01.2013

Самый простой способ сделать это — просмотреть результаты grep.

#!/bin/bash

read -p "Enter title: " TITLE

FILENAME="db.txt"
IFS=$'\n'
for LINE in `grep -iw  "Harry Potter" "$FILENAME"`; do
    echo $LINE | awk 'BEGIN { FS = ":" } ; { print $1, $2, $3, $4, $5 }'
done

Изменение IFS изменяет разделитель на новую строку, а не на пробел, а FS в команде awk изменяет разделитель на: чтобы разрешить доступ к полям

person Tyndyll    schedule 09.01.2013

Я знаю, что вы не указали его, но awk, вероятно, лучший инструмент для этой задачи. Он объединяет cut, sed и grep в один удобный и простой в использовании инструмент. Что ж, удобный инструмент...

Чтобы понять awk, вам нужно понять несколько вещей:

  • Awk — это язык программирования. Он имеет встроенную логику и переменные.
  • Awk предполагает цикл чтения, считывающий каждую строку.
  • Программы Awk должны быть заключены в фигурные скобки.
  • Не только фигурные скобки, но и переменные синтаксического анализа Awk начинаются со знака доллара. Поэтому вам нужно заключать свои программы Awk в одинарные кавычки, чтобы оболочка не попала в них.
  • Awk автоматически анализирует каждую строку на основе разделителя полей. Разделителем полей по умолчанию является пробел, но вы можете изменить его с помощью параметра -f.
  • Каждое поле получает специальную переменную. Первое поле — $1, следующее поле — $2 и т. д. Вся строка — $0.

Вот ваше утверждение Awk:

awk -F: '{
    title =  $1
    author = $2
    price  = $3
    pages_read_until_i_got_bored=$4
    pages = $5
    print "I read " pages_read_until_i_gob_bored "pages out of " $pages " pages of " $title " by " $author "."
}' $file

Конечно, все это может быть и одной строкой:

 awk -F: '{ print "I read " $4 " pages " out of " $5 " of " $1 " by " $2 "." }' $file

Просто хотел подчеркнуть программируемость Awk и то, как его можно использовать для такого типа синтаксического анализа.

Если ваш вопрос заключается в том, как ввести эту информацию и поместить ее в переменные среды, ответ Гленна Джекмана будет лучшим.

person David W.    schedule 09.01.2013

Если вы можете использовать sed, это будет решением

  read -p "Enter title: " TITLE
  sed -n -e 's/^\([^:]\+:\)\{2\}/\0$/' -e 's/:/, /g' -e "/^$TITLE/Ip" db.txt

Краткое объяснение, что он делает

 -n tells sed not to print any lines
 -e 's/^\([^:]\+:\)\{2\}/\0$/' matches for the 2nd : and adds a $ after it
 -e 's/:/, /g' replaces all : with , and a following whitespace
 -e "/^$TITLE/Ip" tells sed to print all lines which start with $TITLE (that's the p) and I tells sed to match case-insensitive
person dwalter    schedule 09.01.2013