torr usando aspas duplas com \! b leva ao comando desconhecido na barra invertida

4

Atualmente, tenho um comando sed que desejo agir no seguinte tipo de texto:

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

Eu posso obter o tipo de resultados que quero com este comando:

sed '/user:/!b;n;n;n;n;n;n;n;n;n;s/.*/\t\tpassword: \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: '!!'

O problema com esse comando é que user: é estático. Eu quero ser capaz de percorrer uma lista de usuários e usar uma variável bash em vez de um usuário específico no comando sed. Para fazer isso, preciso usar aspas duplas em vez de aspas simples. No entanto, quando eu uso este comando:

sed "/user:/!b;n;n;n;n;n;n;n;n;n;s/.*/\t\tpassword: \x27\!\!\x27/g"

Ele reclama do '!' no comando !b sed que estou usando (já que o bash está tentando interpretá-lo) No entanto, se eu escapar assim:

sed "/user:/\!b;n;n;n;n;n;n;n;n;n;s/.*/\t\tpassword: \x27\!\!\x27/g"

Então eu recebo este erro:

sed: -e expression #1, char 6: unknown command: '\'

Como posso fazer isso funcionar?

    
por TopHat 19.12.2017 / 16:37

5 respostas

5

As citações não delimitam campos.

Este é um aspecto importante, mas muitas vezes esquecido, da sintaxe da linguagem shell. O modelo mental que alguém "cita argumentos" é, de fato, simplista e errado. Alguém cita coisas que precisam ser citadas, e isso precisa ser apenas uma parte do que acaba sendo um único argumento.

E é exatamente isso que você precisa fazer aqui. Ironicamente, a primeira resposta agora excluída foi muito próxima .

A string final que você deseja dar a sed como o único argumento real com o qual ele é executado é

/user:/!b;n;n;n;n;n;n;n;n;n;s/.*/\t\tpassword: \x27!!\x27/g
com a parte user variando de acordo com o nome do usuário real. Mas para chegar até aqui você não precisa usar um único esquema de cotação para o argumento inteiro. Você pode construir o argumento a partir de partes.

Use aspas duplas para a parte em que você precisa que a expansão do parâmetro ocorra e aspas simples para as partes em que você precisa expandir o histórico para não ocorrer:

while read -r i
do
   sed -e '/'"$i"':/!b;n;n;n;n;n;n;n;n;n;s/.*/\t\tpassword: \x27!!\x27/g' puppet-users.yaml > puppet-users{new}.yaml
   mv puppet-users{new}.yaml puppet-users.yaml
done < user_list.txt

Isto é:

  1. O caractere com aspas simples / .
  2. Os caracteres com aspas duplas $i , que estão sujeitos à expansão de parâmetros (e, é claro, a todas as outras expansões e substituições).
  3. Os caracteres com aspas simples :/!b;n;n;n;n;n;n;n;n;n;s/.*/\t\tpassword: \x27!!\x27/g que não estão sujeitos à expansão do histórico.

A divisão de campos ocorre apenas em caracteres não citados; e não há nenhum aqui, então este é todo um campo , que se torna todo um argumento para o comando sed .

Depois de fazer isso, você descobrirá que usou muitos caracteres TAB no que está fazendo. ☺

Na mão emocionante ...

sed não é a ferramenta certa para esse trabalho. Também não é awk , como na segunda resposta agora excluída. Analisar o YAML usando um analisador YAML adequado, como o módulo yaml do Python, será melhor a longo prazo, se a tarefa em questão se tornar menos trivial, como provavelmente acontecerá.

E este é um simples one-liner com uma ferramenta adequada para o trabalho, como várias ferramentas yq , cujo uso será mais ou menos (desde que, infelizmente, todas elas diferem):

while read -r i
do
   yq < puppet-users.yaml "$i".password="'\!\!'" > puppet-users{new}.yaml
   mv puppet-users{new}.yaml puppet-users.yaml
done < user_list.txt

Leitura adicional

por 19.12.2017 / 20:24
1

Obrigado ao @steeldriver pela ajuda nos comentários. Acabei resolvendo o problema refatorando meu comando para:

sed "/user:/{n;n;n;n;n;n;n;n;n;s/.*/\t\tpassword: \x27\!\!\x27/g}"

Eu consegui substituir user: por uma variável de script bash.

    
por 19.12.2017 / 18:42
1

Posso sugerir que você use ex , a forma predecessora e não visual de vi , para seus vários requisitos de endereçamento:

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

0 significa linha 0; isto é, comece essa busca a partir do início do arquivo.

/^user:/ é um endereço. Ele funciona de forma semelhante aos endereços Sed ou Awk, exceto que em ex você pode especificar um endereço adicional que é obtido da linha especificada pelo primeiro endereço. (Você pode até usar o endereçamento para trás.)

/password:/ é outro endereço. Então, isso especifica a linha encontrada pesquisando o padrão password: do ponto em que o padrão ^user: foi encontrado.

O comando s/old/new/ funciona exatamente como em Sed.

x significa salvar e sair.

A coisa toda é agrupada em um Bash "aqui doc."

Como apontado nos comentários, o propósito original era fazer uma iteração através de uma lista desses usuários em vez de codificar um usuário estático.

Para fazer isso, supondo um arquivo de texto userlist.txt contendo, por exemplo,

user1
user2

Além disso, assumindo que você deseja executar a mesma substituição para cada usuário (ou seja, definir o campo "senha" como '!!' ), basta usar sed para converter a lista de usuários em um comando ex .

Observe que, como !! será expandido em um shell interativo, para usá-lo interativamente, primeiro execute

set +H

Então:

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

Fácil como torta. ;)

Demonstração:

$ 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'
$ 

Voila.

    
por 20.12.2017 / 01:09
0

A solução sed aqui ficará horrível assim que você precisar pular mais linhas. Aqui está uma solução usando awk :

awk '
/user:/ { 
   NR=0
   edit=1
} 
NR==9 && edit { 
   $0="\tpassword: '\'\!\!\''"  
} 
1' input_file

Ou como um verso:

awk '/user:/{ NR=0; edit=1 } NR==9 && edit{ $0="\tpassword: '\'\!\!\''" } 1' input_file
    
por 19.12.2017 / 20:17
0

! é um caractere de expansão de histórico. A expansão do histórico é um recurso do bash (e de alguns outros shells) quando estão em execução interativa - em um script, ele não tem nenhum significado especial (a menos que você o habilite explicitamente).

! mantém seu significado de expansão de histórico entre aspas duplas. Mas entre aspas duplas, \! significa backslash-bang. Portanto, não há como incluir um ponto de exclamação entre aspas duplas. Você tem que usar alguma outra forma de citar: \! fora de qualquer cotação ou ! entre aspas simples. (Outra solução seria colocar o ! em uma variável.)

Nada impede que você misture formas de cotação na mesma palavra, por exemplo,

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

(Mas eu tenho que concordar com JdeBP , isso não é um trabalho para sed.)

    
por 20.12.2017 / 00:35