使用 python2.7、perl、sed 在 bash 一行中搜索和替换 ini 文件中的项目

Search and replace items within an ini file, using python2.7, perl, sed within a bash one-liner

今天我偶然发现了一个我无法(轻松)解决的小问题。基本上我想更新 ini 文件中的项目,仅使用简单的预安装工具(bash、sed、perl 或 python)。遗憾的是,无法从 Internet 安装工具。

但是我自己想不出解决办法。我已经尝试过 perl 搜索和替换、sed 和 python,但没有任何结果是我一直在寻找的漂亮的 bash 单行代码。已知信息只是用户(例如 john),我想更改 John 的显示名称(仅),其他一切保持原样。我认为 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="

后面写的任何内容

谢谢大家的精彩回答!我对它们进行了所有测试,似乎都按预期工作,并为我提供了学习提示,这样我就可以自己解决这样的任务 :) !感谢您的时间和精力!

类似这样的东西应该可以解决问题:

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

这似乎符合您的要求:

perl -0777 -ne '@sections = split /^\[/m; foreach (@sections) { if (/^\s*user\s*=\s*john\s*$/m) {s/^(\s*displayName\s*=).*/ 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*=).*/ 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

或者在文件 "in-place" 上执行此操作,几乎相同的行:

perl -0777 -i.bak -pe '@sections = split /^\[/m; foreach (@sections) { if (/^\s*user\s*=\s*john\s*$/m) {s/^(\s*displayName\s*=).*/ 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*=).*/ $n/mi} } print join("[", @sections); '
  "john" "My name is Bond..." /tmp/inifile

注意 不依赖于 [section]

中的行顺序的版本参见结尾

对于输入文件完全如图

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

为便于阅读,分多行显示,逐行解释:

  1. 捕获要用于文件的用户名。出现在 ;

  2. 行的开头
  3. 捕获一个部分的用户名(user开始该行),或者$u保持undef

  4. 更改 displayName 的值 --- 如果设置了标志 $f(在上一行)

  5. $f 标志设置为比较结果:true (1) 如果 $u(已设置并且)匹配 $user

程序按如下方式处理文件中的行:

  • ; 行设置了 $user 而在其他行正则表达式不匹配并且没有任何反应

  • 在以 user 开头的行(在节中)设置节的用户名 ($u),在其他行中 $u 变为 undef

  • 如果 $f 为真,则 displayName 发生变化,如果 $u 已设置并且确实等于 $user -- 否则为假。

  • so displayNameuser = 行之后的一行上发生变化,如果在那(前)行上用户 $u 等于找到的用户在开头 ($user).

这自由地使用了通常是 undef 的变量,并且会触发许多 -w 的警告;与我的做法相反,为了简单起见,我将其放在此处(而不是使用 define 进行测试)。

代码假定

  • 用户名定义出现在以;(开头)

  • 开头的行中
  • 在一个部分中,user = 行出现在 displayName = 行之前 --- 但见下文

  • 后面的 displayName 行以该短语开头,前面没有任何内容

  • 部分中的 user = 行以 user

  • 开头
  • 更改displayName =仅在开头捕获用户名的部分

要覆盖输入文件(将其就地更改)添加-i选项,或-i.bak保留备份。要创建具有更改重定向输出的新文件,perl -wpe'...' file > new_file


上面一个必要且可能禁止的假设是 user = 行出现在 displayName = 行之前,这通常不是 ini 文件中的规则。

为了放松,我们需要首先收集user和每个部分displayName,一旦两者都在手上就可以了出来然后才把它们写出来

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

(让它变得更好,特别是摆脱代码重复的 END 块,留作练习)

如果此代码需要存在于 bash 脚本中并且磁盘上不能有额外的文件,那么就这样吧;但除此之外请把它写成一个好的脚本。