Соберите данные (два параметра) между двумя ключевыми словами (переменная строка) из ini-файла

#bash #shell #text #cut #collect

Вопрос:

У меня есть файл txt.ini с содержимым (я не могу изменить структуру этого файла):

txt.ini

txt.ini

 [person_0:public] name=john groups=0,1,2 age=30  [person_0:private] married=false weight=190 height=100  [person_1:public] name=mark groups=0,4 age=28  [person_1:private] married=false weight=173 height=70  [person_2:public] name=tony groups=3,4 age=30  [person_3:private] married=true weight=202 height=120  

У меня есть переменная «человек», которая принимает значение: person_0, person_1, person_3 в цикле, и я хотел бы собирать данные о человеке, такие как возраст и группы для каждого «человека» по одному.

Моя идея состоит в том, чтобы вырезать часть между $person:public и $person:private, а затем собрать

например, установите переменную person=person_1 вывод: группы=0,4 возраст=28

Я подготовил код в bash (persons-это список лиц_0, лиц_1, лиц2):

 for person in ${persons[@]}; do  file="txt.ini"  echo "$person"  a=$(awk -v a=$person":private" -v b=$person":public" '/a/{found=0} {if(found) print} /b/{found=1}' $file)   IFS=

Список групп и возраст пусты. Выход:

 person_0 Group list = Age =  person_1 Group list = Age =  person_2 Group list = Age =  

Expected:

 person_0 Group list =0,1,2 Age =30  person_1 Group list =0,4 Age =28  person_2 Group list =3,4 Age =30  

Я буду использовать эти данные "на человека" в другой части моего кода. Я работаю над файлами с разным количеством "людей".

Я действительно не знаю, что не так.

Я тоже пытался:

 #export person="person_0" #a=$(awk '/ENVIRON["person"]:private/{found=0} {if(found) print} /ENVIRON["person"]:public/{found=1}' $file)  

и

 private=$person":private" public=$person":public" echo "private=$private" echo "public=$public" a=$(awk -v a=$private" -v b=$public '/a/{found=0} {if(found) print} /b/{found=1}' $config_file)  

но результат был тот же самый:

 person_0 private=person_0:private public=person_0:public Group list = Age =  

Что для меня странно - когда я жестко кодирую диапазон резки, он работает правильно:

 a=$(awk '/person_0:private/{found=0} {if(found) print} /person_0:public/{found=1}' $file)  

или

 a=$(awk '/person_1:private/{found=0} {if(found) print} /person_1:public/{found=1}' $file)  

У вас есть какие-либо идеи о том, как я могу собрать эти данные разумным способом?

Комментарии:

1. Каков ожидаемый результат?

2. для человека=персона_0 =gt; группы=0,1,2 возраст=30 и для человека=персона_1 =gt;gt; группы=0,4 возраст 28 и для человека=персона_2 =gt;gt;gt; группы=3,4 возраст=30

3. исправлен переданный код "для человека в ${лиц[@]}; do" лица-это список лиц_0, лиц_1, лиц_2

4. верно, я изменил это

Ответ №1:

Не могли бы вы, пожалуйста, попробовать следующее:

 awk -v RS='' ' # split the records on the blank lines /public/ { # "public" record  split($1, a, /[[:]/); print a[2] # extract the "person_xx" substring  for (i = 2; i lt;= NF; i  ) { # iterate over the lines of the record  split($i, a, /=/)  if (a[1] == "groups") print "Group list =" a[2]  else if (a[1] == "age") print "Age =" a[2]  }  print "" # insert a blank line } ' txt.ini  

Выход:

 person_0 Group list =0,1,2 Age =30  person_1 Group list =0,4 Age =28  person_2 Group list =3,4 Age =30   
  • При установке awk переменной RS в нулевую строку записи разделяются пустыми строками, а поля разделяются символом новой строки.
  • Предполагая, что нужные данные включены в public блок, мы можем анализировать поля public записи одно за другим.

[Править]
Согласно комментарию ОП, вот обновленная версия:

 #!/bin/bash  persons=("person_0") # list of desired person(s) for person in "${persons[@]}"; do # loop over the bash array  awk -v RS='' -v person="$person" ' # assign awk variables  $1 ~ person ":public" { # "public" record of the person  split($1, a, /[[:]/); print a[2] # extract the "person_xx" substring  for (i = 2; i lt;= NF; i  ) { # iterate over the lines of the record  split($i, a, /=/)  if (a[1] == "groups") print "Group list =" a[2]  else if (a[1] == "age") print "Age =" a[2]  }  }  ' txt.ini  echo # insert a blank line done  
  • Вы можете назначить persons массив любому, кому захотите.
  • Шаблон $1 ~ person ":public" проверяет, соответствует ли 1-е поле записи $1 (например [person_0:public] ) переменной awk person (переданной с -v опцией), за которой следует строка ":public".

Очевидно, что сценарий awk повторяет чтение txt.ini файла в несколько раз больше, чем элементов #в persons массиве. Если text.ini файл длинный и/или persons массив содержит много элементов, цикл будет неэффективным. Вот еще один вариант:

 #!/bin/bash  persons=("person_0" "person_1") # bash array just for an example awk -v RS='' -v persons_list="${persons[*]}" '  # persons_list is a blank separated list of persons BEGIN {  split(persons_list, a) # split persons_list back to an array  for (i in a) persons[a[i]] # create a new array indexed by person } /public/ { # "public" record  split($1, a, /[[:]/) # extract the "person_xx" substring  if (a[2] in persons) { # if the person exists in the list  print a[2]  for (i = 2; i lt;= NF; i  ) { # iterate over the lines of the record  split($i, a, /=/)  if (a[1] == "groups") print "Group list =" a[2]  else if (a[1] == "age") print "Age =" a[2]  }  print "" # insert a blank line  } } ' txt.ini  

Пожалуйста, обратите внимание, что предполагается, что строка person не содержит пробелов. Если это так, измените разделитель при назначении persons_list неиспользуемому символу, такому как запятая.

Комментарии:

1. Как я могу собирать данные только от одного человека? Например, person_0 и не печатать все. Я имею в виду, что в верхней части кода у меня есть переменная, которая выбирает одного человека, например. в это время x="person_0", и как я могу установить переменные группы и возраста?

2. Спасибо вам за обратную связь. Я обновил свой ответ. Надеюсь, это соответствует вашим требованиям.

Ответ №2:

Допущения:

  • для данного человека (например, person_0 ) отобразите указанного человека вместе с соответствующими ( public ) полями для groups и age
  • не было дано никаких указаний на то, что мы должны делать с этими данными, поэтому предположим, что на данный момент нам просто нужно распечатать в stdout
  • список лиц, подлежащих обработке, находится в bash массиве persons[]
  • строки :public и :private отображаются только в заголовках блоков

Одна awk идея, в которой мы используем split() функцию для анализа строки на основе разных разделителей:

 awk ' FNR==NR { persons[$1]  next  } /:private/ { printme=0 } /:public/ { printme=0   split($1,arr,"[]:[]")  person=arr[2]   if (person in persons) {  printme=1  printf "%s%sn", pfx, person  pfx="n"  }  } printme { split($1,arr,"=")  if (arr[1] == "groups") print "Group list =" arr[2]  if (arr[1] == "age") print "Age =" arr[2]  } ' lt;(printf "%sn" "${persons[@]}") txt.ini  

Вариация на эту тему с использованием разделителя полей ввода из нескольких символов:

 awk -F"[]:=[]" ' FNR==NR { persons[$1]  next  } $3=="private" { printme=0 } $3=="public" { printme=0  if ($2 in persons) {  printme=1  printf "%s%sn", pfx, $2  pfx="n"  }  } printme amp;amp; $1=="groups" { print "Group list =" $2 } printme amp;amp; $1=="age" { print "Age =" $2 } ' lt;(printf "%sn" "${persons[@]}") txt.ini  

С:

 $ typeset -p persons declare -a persons=([0]="person_0" [1]="person_1" [2]="person_2")  

Оба набора awk кода генерируют:

 person_0 Group list =0,1,2 Age =30  person_1 Group list =0,4 Age =28  person_2 Group list =3,4 Age =30  

ПРИМЕЧАНИЕ: это можно было бы сделать более динамичным ( public и/или private ? в разных областях?) но это повлечет за собой немного больше кодирования

Комментарии:

1. Как я могу собирать данные только от одного человека? Например, person_0 и не печатать все. Я имею в виду, что в верхней части кода у меня есть переменная, которая выбирает одного человека, например. в это время x="person_0", и как я могу установить переменные группы и возраста?

n' lines=($a) IFS='=' read grouplist grouplist_values lt;lt;lt; ${lines[1]} IFS='=' read age age_values lt;lt;lt; ${lines[4]} echo "Group list = $grouplist_values" echo "Age = $age_values" Список групп и возраст пусты. Выход:


Expected:


Я буду использовать эти данные «на человека» в другой части моего кода. Я работаю над файлами с разным количеством «людей».

Я действительно не знаю, что не так.

Я тоже пытался:


и


но результат был тот же самый:


Что для меня странно — когда я жестко кодирую диапазон резки, он работает правильно:


или


У вас есть какие-либо идеи о том, как я могу собрать эти данные разумным способом?

Комментарии:

1. Каков ожидаемый результат?

2. для человека=персона_0 =gt; группы=0,1,2 возраст=30 и для человека=персона_1 =gt;gt; группы=0,4 возраст 28 и для человека=персона_2 =gt;gt;gt; группы=3,4 возраст=30

3. исправлен переданный код «для человека в ${лиц[@]}; do» лица-это список лиц_0, лиц_1, лиц_2

4. верно, я изменил это

Ответ №1:

Не могли бы вы, пожалуйста, попробовать следующее:


Выход:


  • При установке awk переменной RS в нулевую строку записи разделяются пустыми строками, а поля разделяются символом новой строки.
  • Предполагая, что нужные данные включены в public блок, мы можем анализировать поля public записи одно за другим.

[Править]
Согласно комментарию ОП, вот обновленная версия:


  • Вы можете назначить persons массив любому, кому захотите.
  • Шаблон $1 ~ person ":public" проверяет, соответствует ли 1-е поле записи $1 (например [person_0:public] ) переменной awk person (переданной с -v опцией), за которой следует строка «:public».

Очевидно, что сценарий awk повторяет чтение txt.ini файла в несколько раз больше, чем элементов #в persons массиве. Если text.ini файл длинный и/или persons массив содержит много элементов, цикл будет неэффективным. Вот еще один вариант:


Пожалуйста, обратите внимание, что предполагается, что строка person не содержит пробелов. Если это так, измените разделитель при назначении persons_list неиспользуемому символу, такому как запятая.

Комментарии:

1. Как я могу собирать данные только от одного человека? Например, person_0 и не печатать все. Я имею в виду, что в верхней части кода у меня есть переменная, которая выбирает одного человека, например. в это время x=»person_0″, и как я могу установить переменные группы и возраста?

2. Спасибо вам за обратную связь. Я обновил свой ответ. Надеюсь, это соответствует вашим требованиям.

Ответ №2:

Допущения:

  • для данного человека (например, person_0 ) отобразите указанного человека вместе с соответствующими ( public ) полями для groups и age
  • не было дано никаких указаний на то, что мы должны делать с этими данными, поэтому предположим, что на данный момент нам просто нужно распечатать в stdout
  • список лиц, подлежащих обработке, находится в bash массиве persons[]
  • строки :public и :private отображаются только в заголовках блоков

Одна awk идея, в которой мы используем split() функцию для анализа строки на основе разных разделителей:


Вариация на эту тему с использованием разделителя полей ввода из нескольких символов:


С:


Оба набора awk кода генерируют:


ПРИМЕЧАНИЕ: это можно было бы сделать более динамичным ( public и/или private ? в разных областях?) но это повлечет за собой немного больше кодирования

Комментарии:

1. Как я могу собирать данные только от одного человека? Например, person_0 и не печатать все. Я имею в виду, что в верхней части кода у меня есть переменная, которая выбирает одного человека, например. в это время x=»person_0″, и как я могу установить переменные группы и возраста?