У меня есть файл в таком формате:
[#] OWNER_NAME NAME SIZE
[6] Robottinosino Software 200
[42] Robottinosino Ideas worth zero 188
[12] Robottinosino Ideas worth zero or more 111
[13] I am Batman Hardware 180
[25] Robottinosino Profile Pictures 170
и я хотел бы иметь возможность делать следующее с помощью инструментов командной строки:
my_command "Ideas worth zero"
и получаем такой результат:
42
и не рисковать получить такой результат:
12
Я думал использовать grep для идентификации строки и awk для получения первого поля, но я не уверен, как надежно и эффективно выполнить сопоставление по всему полю «NAME», не считая подсчета столбцов, в которых в заголовке появляются тексты «OWNER_NAME» и «SIZE», и получения всего, что находится между ними, с некоторой обрезкой пробелов.
Обратите внимание, что «OWNER_NAME» может состоять из нескольких слов: например, «OWNER_NAME» = «Я — Бэтмен».
Есть ли идеи по их реализации?
Здесь мне придется руководствоваться только старым семейством команд: cat, head, tail, awk, sed, grep, cut и т. д.
решение1
Хорошо, если длина столбцов неизвестна, я бы перешел на более мощный язык, чем bash:
#!/usr/bin/perl
use warnings;
use strict;
my $string = shift;
open my $FH, '<', '1.txt' or die $!;
my $first_line = <$FH>;
my ($before, $name) = $first_line =~ /(.* )(NAME *)/;
my $column = length $before;
$string .= ' ' x (length($name) - length $string); # adjust the length of $string
while (<$FH>) {
if ($column == index $_, $string, $column) {
/^\[([0-9]+)\]/ and print "$1\n";
}
}
решение2
Если ширина полей постоянна, т.е. формат файла, который вы показали, с максимальной шириной полей, вы можете использовать GNU awk ( gawk(1)
) и задать FIELDWIDTHS
переменную для использования анализа с фиксированной шириной:
gawk -v searchstr="Ideas worth zero" -- '
BEGIN { FIELDWIDTHS="6 15 27 5" } # assuming the final field width is 5
# Pre-process data
{
gsub(/[^[:digit:]]/, "", $1) # strip out non-numbers
for (i = 2; i <= NF; i++)
gsub(/[[:space:]]*$/, "", $i) # strip trailing whitespace
}
# match here
$3 == searchstr { print $1 }
' file.txt
Вы можете обернуть это в скрипт оболочки или функцию и параметризовать searchstr
( -v searchstr="$1"
).
Однако, если поля имеют переменную ширину, то есть если данные изменяются, ширина полей может измениться, вам нужно будет быть немного умнее и динамически определять ширину полей, просматривая первую строку. Учитывая, что одно поле называется OWNER_NAME
, используя подчеркивание, я предполагаю, что пробелы в именах полей отсутствуют, поэтому я могу предположить, что пробелы разделяют имена полей.
Определив это, вы можете заменить BEGIN...
строку следующим кодом:
NR == 1 {
for (i = 2; i <= NF; i++)
FIELDWIDTHS=FIELDWIDTHS index($0" ", " "$i" ")-index($0" ", " "$(i-1)" ") " "
FIELDWIDTHS=FIELDWIDTHS "5" # assuming 5 is the width of the last field
next
}
Это позволит просмотреть поля в первой строке и вычислить ширину полей, вычислив разницу между позициями последующих полей для второго и последнего поля. Я предположил, что ширина последнего поля равна 5, но я думаю, что вы можете просто поставить там большое число, и это будет работать с тем, что осталось.
Нам нужно найти пробел до и после имени, чтобы убедиться, что мы не найдем его NAME
внутри OWNER_NAME
(или если там было поле с именем OWNER
), а вместо этого сопоставить все поле (нам также нужно добавить пробел, чтобы $0
убедиться, что мы можем сопоставить пробел в конце, даже если там его нет).
Вы могли бы пойти более хитрым путем и делать запросы по имени поля, а не только по $3
, но я оставлю это на ваше усмотрение.
решение3
Вероятно, проще всего сначала отфильтровать строки по критерию «Идеи, стоящие ноль», а затем отбросить строки «... или больше»:
grep 'Ideas worth zero' | grep -v 'Ideas worth zero or more'
И чтобы получить число из этой трубы, введите:
cut -d' ' -f1 | tr -d ']['
Что вырезает первое поле (разделенное пробелом) и удаляет квадратные скобки.
Лучше всего будет немного изменить формат файла таким образом, чтобы он содержал правильные разделители полей.
решение4
Это может вам помочь:
function my_command () {
sed -n $(cut -b22-48 1.txt |
grep -n "$1"' *$' |
cut -f1 -d: )p 1.txt \
| cut -d' ' -f1 | tr -d ']['
}
Он вырезает из входных данных только соответствующий столбец, ищет номер строки, в которой появляется строка, затем берет эту строку и оставляет из нее только номер в первом столбце.