Поиск и замена элементов в INI-файле, используя python2.7, perl, sed в пределах одной строки bash - PullRequest
1 голос
/ 24 сентября 2019

Сегодня я наткнулся на небольшую проблему, которую не мог решить (легко).По сути, я хочу обновить элемент в INI-файле, используя только простые предустановленные инструменты (bash, sed, perl или python).К сожалению, установка инструментов из интернета не вариант.

Но я не мог найти решение самостоятельно.Я пробовал поиск и замену perl, sed и python, но ничто не приводит к хорошему bash one-liner, который я искал.Известная информация - только пользователь (например, Джон), и я хочу изменить displayName Джона (только), оставив все остальное, как было.Я думаю, что Perl-вызов мог бы добиться цели, но я не смог понять это.В любом случае, как бы вы подошли к разработке такого perl-оператора?

; last modified 1 April 2001 by John Doe
; Syntax:
; user = john
; displayName = 12345

[sectionA]
user=notChange
displayName=noChange
[sectionB]
user = john
displayName = 12345
.....

Я хотел бы проанализировать файл на "john" и затем заменить все, что написано за "displayName ="

Спасибо, ребята, за все отличные ответы!Я проверил их все, и все, кажется, работают как ожидалось, и дают подсказки для меня, чтобы однажды я мог решить такую ​​задачу самостоятельно :)!Спасибо за ваше время и усилия!

Ответы [ 3 ]

3 голосов
/ 24 сентября 2019

Это похоже на то, что вам нужно:

perl -0777 -ne '@sections = split /^\[/m; foreach (@sections) { if (/^\s*user\s*=\s*john\s*$/m) {s/^(\s*displayName\s*=).*/$1 is now Greta/mi} } print join("[", @sections); ' /tmp/inifile

Или в более читаемой версии:

perl -0777 -ne '                                    # -0777 makes it slurp the whole file
  @sections = split /^\[/m;                       # split on "[" if it's at the start of a line
  foreach (@sections) {
    if ( /^\s*user\s*=\s*john\s*$/m ) {              # if the section contains user=john
      s/^(\s*displayName\s*=).*/$1 call me Greta/mi;  # give him a new displayname
    }
  }
  print join("[", @sections);     # print all, adding back the "[" we removed for split.
' /tmp/inifile 

Вывод:

; last modified 1 April 2001 by John Doe
; Syntax:
; user = john
; displayName = 12345

[sectionA]
user=notChange
displayName=noChange
[sectionB]
user = john
displayName = call me Greta

Или сделать это для файлов "на месте", с почти одинаковой строкой:

perl -0777 -i.bak -pe '@sections = split /^\[/m; foreach (@sections) { if (/^\s*user\s*=\s*john\s*$/m) {s/^(\s*displayName\s*=).*/$1 call me Greta/mi} } $_=join("[", @sections); ' /tmp/inifile 

Что дает:

diff /tmp/inifile /tmp/inifile.bak 
11c11
< displayName = call me Greta
---
> displayName = 12345

И чтобы указать пользователя и отображаемое имя в командной строке вместо того, чтобы жестко их кодировать, добавьте их перед именем файла и добавьте блок BEGIN:

perl -0777 -ne 'BEGIN{$u=shift; $n=shift}
  @sections = split /^\[/m; foreach (@sections) { if (/^\s*user\s*=\s*$u\s*$/m) {s/^(\s*displayName\s*=).*/$1 $n/mi} } print join("[", @sections); '
  "john" "My name is Bond..." /tmp/inifile
3 голосов
/ 24 сентября 2019

Примечание См. Конец для версии, которая не зависит от порядка строк в [section]


Для входного файла точно , как показано

perl -pE'
    /;\s*user\s*=\s*(.*)/ and $user = $1; 
    ($u) = /^user\s*=\s*(.*)/;
    s/^\s*displayName\s*=\s*\K.*/$user/ if $f; 
    $f = $u eq $user
' file

Показано в нескольких строках для удобства чтения, пояснения построчно:

  1. Захватывает имя пользователя, которое будет использоваться для файла.Идет в начале в строках с ;

  2. Захватить имя пользователя в разделе (user начинается с строки) или $u остается undef

  3. Измените значение для displayName --- если установлен флаг $f (в предыдущей строке)

  4. Установите для этого флага $f значениерезультат сравнения: true (1), если $u (установлено и) соответствует $user

Программа работает по строкам файла следующим образом:

  • в строке ; устанавливается $user, в то время как в других строках регулярное выражение не совпадает и ничего не происходит

  • в строках, начинающихся с user(в разделах) задается имя пользователя раздела ($u), в других строках $u становится undef

  • изменение displayName происходит, если $f истинно, и это было установлено в предыдущей строке, если было установлено $u и действительно было равно $user - в противном случае это ложно.

  • , поэтому displayName изменяется на строке, следующей за строкой user =, и если на этой (предыдущей) строке пользователь $u был равен найденному в начале ($user).

Это свободно использует переменные, которые часто undef и будут вызывать многочисленные предупреждения с -w;вопреки моей практике, для простоты я опускаю это здесь (вместо тестирования с define).

Код предполагает, что

  • определение имени пользователя предшествует его использованиюв строке, начинающейся с ; (в начале)

  • в сечении строка user = предшествует строке displayName = --- но см. ниже

  • строки displayName, которые идут позже, начинаются с фразы, перед ней ничего не стоит

  • user = строки в разделах начинаются с user

  • изменить displayName = только в разделе с именем пользователя, записанным в начале

Перезаписать входной файл (изменить его в-place ) добавить опцию -i или -i.bak, чтобы сохранить резервную копию.Чтобы создать новый файл с выходом перенаправления изменений, perl -wpe'...' file > new_file


Одно из необходимых и, возможно, запретительных допущений выше состоит в том, что строка user = стоит перед строкой displayName =, что в общем случае не являетсяправило в файле ini.

Чтобы расслабиться, нам нужно сначала собрать строки для user и displayName для каждого раздела, и как только оба находятся на ручной работеэто и только потом записывать их

perl nE'
    /;\s*user\s*=\s*(.*)/ and $user = $1; 
    if ( 2 == ( @k = keys %sec ) ) { 
        say "$_ = ", ( $sec{user} eq $user ? $user : $sec{$_} ) for @k;
        %sec = () 
    } else { 
        /^(user|displayName)\s*=\s*(.*)/ and $sec{$1} = $2
    }
    print $_ unless /^(user|displayName)/;
    END { 
        say "$_ = ", ( $sec{user} eq $user ? $user : $sec{$_} ) for keys %sec
    }
' file

(Создание этого более приятного, в частности, избавление от этого повторяющего код блока END, остается в качестве упражнения)

Если этот код нуждаетсяжить внутри скрипта bash и никаких дополнительных файлов на диске быть не может, пусть будет так;но в противном случае, пожалуйста, напишите это как хороший сценарий.

2 голосов
/ 24 сентября 2019

Что-то похожее на это должно сработать:

$ perl -i.bak -pe'$user = $1 if /user = (.*)/; s/(displayName = ).*/${1}newid/ if $user eq "john"' foo.ini
...