LINUX.ORG.RU

Парсинг файла в bash

 


0

1

Доброго времени суток, коллеги! Как правильно распарсить файл, имеющий вот такие данные:

inetnum:        194.206.161.47 - 194.206.161.47
netname:        FR-RPN-HOLDING
descr:          RPN Holding
descr:          ZI nord BP 7132
descr:          01007
descr:          Bourg en Bresse
country:        FR
admin-c:        DUMY-RIPE
tech-c:         DUMY-RIPE
status:         ASSIGNED PA
mnt-by:         OLEANE-NOC
created:        1970-01-01T00:00:00Z
last-modified:  2001-09-21T22:08:01Z
source:         RIPE
remarks:        ****************************

inetnum:        213.159.160.0 - 213.159.191.255
netname:        SE-ERICSSON-20010504
country:        DK
org:            ORG-EA44-RIPE
admin-c:        DUMY-RIPE
tech-c:         DUMY-RIPE
status:         ALLOCATED PA
mnt-by:         RIPE-NCC-HM-MNT
mnt-by:         ERICSSON-MNT
created:        1970-01-01T00:00:00Z
last-modified:  2016-06-29T09:54:17Z
source:         RIPE
remarks:        ****************************

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

Как правильно распарсить файл

Правильно – не через Bash.

EXL ★★★★★
()
$ echo "inetnum:        194.206.161.47 - 194.206.161.47\nnetname:        FR-RPN-HOLDING" | while read -r l; do NAME=$(echo $l | awk '{print $1}' | sed 's/://'); VALUE=$(echo $l | awk '{print $2}'); declare $NAME=$VALUE; done 
$ echo "inetnum:        194.206.161.47 - 194.206.161.47\nnetname:        FR-RPN-HOLDING" | while read -r l; do NAME=$(echo $l | awk '{print $1}' | sed 's/://'); VALUE=$(echo $l | awk '{print $2}'); declare $NAME=$VALUE; done      
$ echo $inetnum 
194.206.161.47
$ echo $netname
FR-RPN-HOLDING
$
urxvt ★★★★★
()

Ну типа

IFS=':'; while read -r key value; do echo "$key" "$value"; done < <(whois 194.206.161.47 | grep ':')

slowpony ★★★★★
()
IFS=':'; while read -r key value; do 
   if [[ "$key" == "inetnum" ]]; then 
      echo "$value"; 
   fi; 
done < <(whois 194.206.161.47 | grep ':')
slowpony ★★★★★
()

Во-первых, как уже выше сказали, парсить это башем это уже порочная идея. Во-вторых,

что нибудь с ними сделать

это будет ещё сложнее. Формат whois-записей весьма вольный (а если захочешь объединять европейские и например американские записи, так вообще ужас - у них считай почти разные форматы), так ещё и не всегда корректно заполнены. Без нормального языка программирования будет весьма сложно получить вменяемый результат.

firkax ★★★★★
()

while read var value; do
   var=${var%:}
   var=${var/-/_}
   [ -n "$var" ] && eval "${var}=\"$value\""
done <  input.txt


$ echo $inetnum
213.159.160.0 - 213.159.191.255

$ echo $netname
SE-ERICSSON-20010504

одинаковые поля будут иметь последнее значение или можно соединять

eval "${var}=\"${var} $value\""
futurama ★★★★★
()
Ответ на: комментарий от firkax

Во-первых, как уже выше сказали, парсить это башем это уже порочная идея.

Я смотрю там уже в тему некорректных сообщений портянко-фанатики побежали стучать что их Bash оскорбили, а между прочим тут уже eval начали советовать для решения этой задачи.

Ну что, рискнёт кто-нибудь проверить что там будет, если какой-нибудь шутник сделает в remarks своего домена так:

remarks:        `rm -Rf <твой любимый хомячок>`

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

Да. И обычно для нормальных языков программирования для парсинга подобного формата насыпали батареек:

  1. https://pypi.org/project/python-whois
  2. https://github.com/weppos/whois-parser

Ведь тысячи их, выбирай под свой любимый нормальный язык. А если не доверяешь сторонним библиотекам, почитай их код (наверняка там простейший парсер) который можно быстро реимплементировать или перенести на свой любимый язык.

EXL ★★★★★
()

Ну нате даже с комментариями:

#!/usr/bin/env bash

# Интересные нам поля (вывод программы будет в указанном порядке)
declare -a fields=(
			inetnum netname descr country org
			admin-c tech-c mnt-by
			status last-modified source
		   )

# Ассоциативный массив с данными
declare -A data

# Список сетей. Считаем, что первая запись это сеть
declare -a nets


# Трюк для последующей быстрой проверки о том, что нас интересует это поле
for idx in "${fields[@]}"; do
	data["$idx"]=1
done

IFS=$': \t'
key=
while read -r var value; do
	if [[ -z $var ]]; then
		# пустая строка - конец одной порции данных
		key=
		continue
	fi

	# проверяем, что нас интересует это поле
	[[ ${data["$var"]} ]] || continue

	# Устанавливаем ключ из значения первой записи (о сети)
	if [[ -z $key ]]; then
		key=$value
		nets+=("$key")
	fi

	# формируем индекс ассоциативного массива для сохранения данных
	idx="$var:$key"
	# склеиваем одинаковые строки по первому полю через пробел
	data["$idx"]+="${data["$idx"]:+ }$value"
done

# по всем ключам (сетям)
for key in "${nets[@]}"; do
	# по всем интересесуемым полям
	for var in "${fields[@]}"; do
		value=${data["$var:$key"]}
		# если запись не пуста, выводим имя поля и его значение
		[[ $value ]] && echo "$var: $value"
	done
	echo
done

Если делать реально с именами, получаемыми из первого поля, то без eval обойтись трудно. Но эта глобальная универсальность непонятно куда применима будет. Я пробовал, оно работает, но выглядит не очень и смысла особого нет.

vodz ★★★★★
()

Я бы вместо регулярных выражений использовал функцию split из Perl для массива строк foreach @myfile. Не, ну это реально эффективнее будет.

cadaber ★★
()
Последнее исправление: cadaber (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.