Como a saída sed pode ser formatada como a impressão formatada do printf?

7

O sed pode substituir o texto por uma string formatada como a impressão formatada do printf?

O seguinte comando sed substitui uma linha que começa com o valor atual de "$ domain" com vários valores especificados em variáveis.

/bin/sed  "s/\(^${domain} *${limittype} * ${limititem}.*\)/$EXPL#\n${domain} ${limittype} ${limititem} ${value}/" /etc/security/limits.conf

No entanto, a saída não será alinhada corretamente porque o tamanho dos valores do domínio, etc., não é o mesmo.

Assim, a saída seria algo como

#oracle   hard   nproc    131072
oracle hard nproc 666

Embora seja válido, é difícil de ler. Eu preferiria algo como

#oracle   hard   nproc    131072
oracle   hard   nproc    666

O melhor que posso usar para obter o resultado desejado é:

/bin/sed  "s/\(^${domain}\)\( *\)\(${limittype}\)\( *\)\(${limititem}\)\( *\)\(.*\)/$EXPL#\n${domain}${limittype}${limititem}${value}/" /etc/security/limits.conf

Mas acredito que deve haver uma maneira mais elegante de fazer isso.

O documento sed one liners contém alguns exemplos que usam um número especificado de caracteres, por exemplo,

sed -e :a -e 's/^.\{1,78\}$/ &/;ta'  # set at 78 plus 1 space

Mas isso está na seção regexp que não está na seção replacement .

    
por Bram 31.05.2012 / 15:16

3 respostas

4

Isso usa a sintaxe de regex estendida -r , o que elimina muito da desordem. Além disso, como você já conhece alguns dos valores de campo, na verdade, você não precisa fazer referência de volta a eles, novamente reduzindo a desordem (e a sobrecarga).

& é um valor especial de substituição: ele contém todo o padrão correspondente. Usando o & , novamente reduz a desordem. Como não é uma referência inversa, tem significativamente menos sobrecarga.

Eu usei ( +) vs. ( *) . O + pressupõe que haja pelo menos um espaço entre os campos de entrada. Basta alterá-lo para o * que não é o caso.

EXPL=
dom=oracle
typ=hard
itm=nproc
val=666

echo "oracle   hard   nproc    131072" |
  sed -r "s/^$dom( +)$typ( +)$itm( +).*/$EXPL#&\n$dom$typ$itm$val/" 

saída

#oracle   hard   nproc    131072
oracle   hard   nproc    666
    
por 01.06.2012 / 01:01
6

Enquanto você teoricamente pode fazer isso inteiramente no sed (já que é Turing-completo), esta não é a ferramenta certa para o trabalho.

Uma maneira fácil é inserir guias no sed e depois processá-las nos espaços. Se você puder determinar a posição de todas as colunas, canalize a saída do sed através de expand .

</etc/security/limits.conf \
sed  "s/\(^${domain} *${limittype} * ${limititem}.*\)/$EXPL#\n${domain}\t${limittype}\t${limititem}\t${value}/" |
expand -t 10,17,26

(Use um caractere de tabulação literal em vez de \t se o seu sed não suportar \t .)

Se você não souber antecipadamente as larguras das colunas, tente o BSD column utilitário. Ele verifica todo o arquivo de entrada para determinar as larguras de coluna que acomodam o comprimento de todas as linhas.

</etc/security/limits.conf \
sed  "s/\(^${domain} *${limittype} * ${limititem}.*\)/$EXPL# ${domain} ${limittype} ${limititem} ${value}/" |
column -t

Se o seu script sed reconfigura linhas comentadas e não comentadas, ou se você usa column , você precisará de um pouco de pós-processamento para distorcer as linhas comentadas pela largura de o marcador de comentário.

… | sed '/^#/ s/ //'

Você pode usar o awk em vez disso. Tem uma função printf . Como um bônus adicional, existe uma maneira fácil de proteger caracteres especiais, como . ou * , em um conteúdo de coluna procurado.

</etc/security/limits.conf awk -v domain="$domain" -v limittype="$limittype" -v limititem="$limititem" -v value="$value" '
$1 == domain && $2 == limittype && $3 == limititem  {
    printf "#%-9s %-8s %-9s %s\n%-9s %-8s %-9s %s\n", $1, $2, $3, $4, $1, $2, $3, value; next
}
1 {print}
'
    
por 01.06.2012 / 01:40
3

Use printf apenas para formatar a saída sed:

printf "%5s %12s %4s\n" $(sed 's/.../.../')
    
por 31.05.2012 / 15:26