使用 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
为便于阅读,分多行显示,逐行解释:
捕获要用于文件的用户名。出现在 ;
行的开头
捕获一个部分的用户名(user
开始该行),或者$u
保持undef
更改 displayName
的值 --- 如果设置了标志 $f
(在上一行)
将 $f
标志设置为比较结果:true (1) 如果 $u
(已设置并且)匹配 $user
程序按如下方式处理文件中的行:
在 ;
行设置了 $user
而在其他行正则表达式不匹配并且没有任何反应
在以 user
开头的行(在节中)设置节的用户名 ($u
),在其他行中 $u
变为 undef
如果 $f
为真,则 displayName
发生变化,如果 $u
已设置并且确实等于 $user
-- 否则为假。
so displayName
在 user =
行之后的一行上发生变化,如果在那(前)行上用户 $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 脚本中并且磁盘上不能有额外的文件,那么就这样吧;但除此之外请把它写成一个好的脚本。
今天我偶然发现了一个我无法(轻松)解决的小问题。基本上我想更新 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
为便于阅读,分多行显示,逐行解释:
捕获要用于文件的用户名。出现在
;
行的开头
捕获一个部分的用户名(
user
开始该行),或者$u
保持undef
更改
displayName
的值 --- 如果设置了标志$f
(在上一行)将
$f
标志设置为比较结果:true (1) 如果$u
(已设置并且)匹配$user
程序按如下方式处理文件中的行:
在
;
行设置了$user
而在其他行正则表达式不匹配并且没有任何反应在以
user
开头的行(在节中)设置节的用户名 ($u
),在其他行中$u
变为undef
如果
$f
为真,则displayName
发生变化,如果$u
已设置并且确实等于$user
-- 否则为假。so
displayName
在user =
行之后的一行上发生变化,如果在那(前)行上用户$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 脚本中并且磁盘上不能有额外的文件,那么就这样吧;但除此之外请把它写成一个好的脚本。