引用不分隔字段。

引用不分隔字段。

我目前有一个 sed 命令,我想对以下类型的文本执行操作:

user:
        ensure: 'present'
        uid: '666'
        gid: '100'
        home: '/home/example'
        comment: ''
        password_max_age: '99999'
        password_min_age: '0'
        shell: '/bin/false'
        password: ''

我可以使用此命令获得我想要的结果类型:

sed '/用户:/!b;n;n;n;n;n;n;n;n;n;s/.*/\t\t密码: \x27\!\!\x27/g'

user:
        ensure: 'present'
        uid: '666'
        gid: '100'
        home: '/home/example'
        comment: ''
        password_max_age: '99999'
        password_min_age: '0'
        shell: '/bin/false'
        password: '!!'

该命令的问题在于它user:是静态的。我希望能够迭代用户列表并使用 bash 变量而不是 sed 命令中的特定用户。但要做到这一点,我需要使用双引号而不是单引号。但是,当我使用这个命令时:

sed "/用户:/!b;n;n;n;n;n;n;n;n;n;s/.*/\t\t密码: \x27\!\!\x27/g"

它抱怨“!”在!b我正在使用的 sed 命令中。(因为 bash 试图解释它)但是,如果我像这样转义它:

sed "/用户:/\!b;n;n;n;n;n;n;n;n;n;s/.*/\t\t密码: \x27\!\!\x27/g"

然后我得到这个错误:

sed:-e 表达式 #1,字符 6:未知命令:`\'

我怎样才能让它发挥作用?

答案1

引用不分隔字段。

这是 shell 语言语法的一个重要但经常被遗忘的方面。 “引用论据”的思维模式其实是简单化的、错误的。一个人引用了需要引用的东西,而且只需要引用部分最终成为一个单一的论点。

这正是您需要在这里做的。讽刺的是,第一个现已删除的答案是很接近

sed您想要作为其执行的实际单个参数提供的最终字符串是

/用户:/!b;n;n;n;n;n;n;n;n;n;s/.*/\t\t密码: \x27!!\x27/g
user部分根据实际用户名而变化。但为了到达这里你不要需要对整个参数使用一种单引号方案。你可以从各个部分建立论据。

对于需要进行参数扩展的部分使用双引号,对于需要历史扩展的部分使用单引号不是发生:

读-ri时
   sed -e '/'"$i"':/!b;n;n;n;n;n;n;n;n;n;s/.*/\t\t密码: \x27!!\x27 /g' puppet-users.yaml > puppet-users{new}.yaml
   mv puppet-users{new}.yaml puppet-users.yaml
完成 < user_list.txt

这是:

  1. 单引号字符/
  2. 双引号字符$i受参数扩展(当然还有所有其他扩展和替换)的影响。
  3. :/!b;n;n;n;n;n;n;n;n;n;s/.*/\t\tpassword: \x27!!\x27/g不受历史扩展影响的单引号字符。

字段分割仅发生在未加引号的字符上;这里根本没有这样的,所以这是全部一个字段,这变成所有一个论点sed命令。

完成此操作后,您会发现您在所做的事情中使用了太多 TAB 字符。 ☺

紧握的手上……

sed不太适合这项工作的工具。也确实不是awk,如第二个现已删除的答案。从长远来看,如果手头的任务变得不那么琐碎,那么使用适当的 YAML 解析器(例如 Python 的 yaml 模块)解析 YAML 会更好。

和这个一个简单的单行文字,带有适合该工作的适当工具,例如各种yq工具,其用法大致如下(因为,唉,它们都不同):

读-ri时
   yq < puppet-users.yaml "$i".password="'\!\!'" > puppet-users{new}.yaml
   mv puppet-users{new}.yaml puppet-users.yaml
完成 < user_list.txt

进一步阅读

答案2

感谢@steeldriver 在评论中提供的帮助。我最终通过将命令重构为以下方式解决了该问题:

sed "/用户:/{n;n;n;n;n;n;n;n;n;s/.*/\t\t密码:\x27\!\!\x27/g}"

然后我可以user:用 bash 脚本变量替换。

答案3

我可以建议你使用ex,前身和非视觉形式vi,满足您的多种寻址要求:

ex file.txt <<'EOF'
0/^user://password:/s/:.*/: '!!'/
0/^something else://subline to change:/s/:.*/: new value/
x
EOF

0表示第 0 行;也就是说,从文件的开头开始搜索。

/^user:/是一个地址。它的工作方式与 Sed 或 Awk 地址类似,不同之处在于ex您可以指定一个额外的地址取自第一个地址指定的行。 (您甚至可以使用向后寻址。)

/password:/其他地址。因此,这指定了从发现password:模式的点开始向前搜索模式所找到的线。^user:

s/old/new/命令的工作方式与 Sed 中相同。

x意思是保存并退出。

整个事情被包裹在一个重击“这里,医生。”


正如评论中指出的,最初的目的是迭代此类用户的列表,而不是对静态用户进行硬编码。

为此,假设一个文本文件userlist.txt包含例如

user1
user2

并进一步假设您要对每个用户执行相同的替换(即将“密码”字段设置为'!!'),只需使用 sed 将用户列表转换为ex命令即可。

请注意,由于!!将在交互式 shell 中展开,因此要交互式地使用它,请首先运行

set +H

然后:

< userlist.txt sed -e "s_.*_0/^&://password:/s/:.*/: '!!'/_" -e $'$a\\\nx' | ex file.txt

易如反掌。 ;)


示范:

$ cat file.txt 
user1:
        ensure: 'present'
        uid: '666'
        gid: '100'
        home: '/home/example1'
        comment: ''
        password_max_age: '99999'
        password_min_age: '0'
        shell: '/bin/false'
        password: '1'
user2:
        ensure: 'present'
        uid: '667'
        gid: '100'
        home: '/home/example2'
        comment: ''
        password_max_age: '99999'
        password_min_age: '0'
        shell: '/bin/false'
        password: '2'
user3:
        ensure: 'present'
        uid: '668'
        gid: '100'
        home: '/home/example3'
        comment: ''
        password_max_age: '99999'
        password_min_age: '0'
        shell: '/bin/false'
        password: '3'
user4:
        ensure: 'present'
        uid: '669'
        gid: '100'
        home: '/home/example4'
        comment: ''
        password_max_age: '99999'
        password_min_age: '0'
        shell: '/bin/false'
        password: '4'
$ cat userlist.txt 
user3
user2
$ set +H
$ < userlist.txt sed -e "s_.*_0/^&://password:/s/:.*/: '!!'/_" -e $'$a\\\nx' | ex file.txt
$ cat file.txt 
user1:
        ensure: 'present'
        uid: '666'
        gid: '100'
        home: '/home/example1'
        comment: ''
        password_max_age: '99999'
        password_min_age: '0'
        shell: '/bin/false'
        password: '1'
user2:
        ensure: 'present'
        uid: '667'
        gid: '100'
        home: '/home/example2'
        comment: ''
        password_max_age: '99999'
        password_min_age: '0'
        shell: '/bin/false'
        password: '!!'
user3:
        ensure: 'present'
        uid: '668'
        gid: '100'
        home: '/home/example3'
        comment: ''
        password_max_age: '99999'
        password_min_age: '0'
        shell: '/bin/false'
        password: '!!'
user4:
        ensure: 'present'
        uid: '669'
        gid: '100'
        home: '/home/example4'
        comment: ''
        password_max_age: '99999'
        password_min_age: '0'
        shell: '/bin/false'
        password: '4'
$ 

瞧。

答案4

!是一个历史扩展角色。历史扩展是 bash(以及其他一些 shell)交互运行时的一个功能 - 在脚本中它没有特殊含义(除非您明确启用它)。

!在双引号内保留其历史扩展含义。但在双引号内,\!意味着反斜杠。因此无法在双引号内包含感叹号。您必须使用其他形式的引用:要么\!在任何引号之外,要么!在单引号内。 (另一种解决方案是将 放入!变量中。)

没有什么可以阻止您在同一个单词中混合引用形式,例如。

sed "/$username:/"\!"b;n;n;n;n;n;n;n;n;n;s/.*/\t\tpassword: '"\!\!"'/g"

(但我必须同意杰德BP,这不是 sed 的工作。)

相关内容