Escape independente de shell para strings em scripts

3

Eu apenas lutei (novamente) com isso:

# only in bash
NORM=$'3[0m'
NORM=$'\e[0m'

# only in dash
NORM='3[0m'

# only in bash and busybox
NORM=$(echo -en '3[0m')

O objetivo é incluir caracteres especiais na string, não apenas para saída usando echo , mas também para canalizar para uma ferramenta cli, etc.

No caso de uso específico acima, usar $(tput ...) é provavelmente a melhor maneira, mas peço uma solução geral de escape com requisitos mínimos para ferramentas externas, mas com compatibilidade máxima.

Normalmente, eu me ajusto usando condições como [ -z "$BASH_VERSION" ] , mas

  1. Não encontrei uma maneira fácil de detectar o busybox ainda
  2. uma atribuição de variável normal em 5 linhas (usando if / else / fi) parece um overkill
  3. Eu prefiro soluções simples
por Daniel Alder 21.10.2015 / 12:48

2 respostas

8

O que você quer é "$(printf ...)" . Stephane já escreveu uma excelente exposição de printf vs echo , mais de um artigo que uma mera resposta, então não vou repetir a coisa toda aqui. As notas chaves pertinentes à questão atual são:

  1. Fique at recursos POSIX e é muito portátil e
  2. Frequentemente é um shell embutido, caso em que você não tem chamadas ou dependências externas.

Eu também adicionarei que demorei um bom tempo (ok, apenas algumas semanas) para mudar de echo , porque eu estava acostumado com echo e achei que printf seria complicado. (O que são todos aqueles % sinais sobre, hein?) Acontece que, printf é realmente extremamente simples e eu não me incomodo com echo mais para nada, mas o texto fixo com uma nova linha no final. / p>

Printf Made Easy

Há um grande número de opções para printf . Você pode imprimir números para casas decimais específicas de precisão. Você pode imprimir vários campos, cada um com uma largura fixa especificada (ou uma largura mínima ou largura máxima). Você pode fazer com que uma variável de seqüência de caracteres de shell que contenha as seqüências de caracteres \t ou \n seja impressa com as seqüências de caracteres interpretadas como guias e novas linhas.

Você pode fazer todas essas coisas e saber que elas são possíveis para que você possa consultá-las quando precisar delas, mas na maioria dos casos, o seguinte será tudo o que você precisa saber:

printf toma como primeiro argumento uma string chamada "format". A string de formatação pode especificar como argumentos adicionais devem ser tratados (por exemplo, como eles serão formatados). Outros argumentos, se não forem referenciados * no argumento format, são ignorados .

Como caracteres alfanuméricos (e outros) podem ser incorporados no argumento de formato e serão impressos como estão, pode parecer como printf está fazendo a mesma coisa que echo -n , mas para alguma razão desconhecida está ignorando tudo menos o primeiro argumento. Isso não é realmente o caso.

Por exemplo, tente printf some test text . Neste exemplo some é realmente tomado como o formato , e como não contém nada de especial e não diz a printf o que fazer com o restante dos argumentos, eles são ignorados e tudo que você imprime é some .

% seguido por uma letra específica precisa ser usado dentro da string de formato (o primeiro argumento para printf ) para especificar que tipo de dados os argumentos subsequentes contêm. %s significa "string" e é o que você usará com mais frequência.

\n ou \t no formato traduzem para novas linhas e caracteres de tabulação, respectivamente.

Isso é tudo que você precisa para usar printf produtivamente. Veja o seguinte bloco de código para alguns exemplos ilustrativos muito simples.

$ var1="First"
$ var2="Second"
$ var3="Third"
$ printf "$var1" "$var2" "$var3"       # WRONG
First$                                 # Only the first arg is printed, without a trailing newline
$
$ printf '%s\n' "$var1"                # %s means that the next arg will be formatted as a literal string with any special characters printed exactly as-is.
First
$
$ printf '%s' "$var1" "$var2" "$var3"  # When more args are included than the format calls for, the format string is reused.  This example is equivalent to using '%s%s%s' as the format.
FirstSecondThird$                      # All three args were printed; no trailing newline.
$
$ printf '%s\t%s\t%s\n' "$var1" "$var2" "$var3"
First   Second  Third                  # Tab separation with trailing newline.  This format is very explicit about what to do with three arguments.  Now see what happens if four are used:
$ var4="Fourth"
$ printf '%s\t%s\t%s\n' "$var1" "$var2" "$var3" "$var4"
First   Second  Third             # The specified format is reused after the three expected args,
Fourth                            # so this line has two trailing tabs.
$
$ printf '%s\n' "$var1" "$var2" "$var3"  # This format reuse can be used to advantage in printing a list, for example.
First
Second
Third
$
$ printf '%s\t' "$var1" "$var2" "$var3" ; printf '\n'  # Here is a dual command that could have args added without changing the format string...
First   Second  Third   
$ printf '%s\t' "$var1" "$var2" "$var3" "$var4" ; printf '\n'
First   Second  Third   Fourth              # ...as you can see here.
$                                           # It does print a trailing tab before the newline, however.

* É claro que, se você incluir uma única sequência de especificador de formato de argumento, como %s , toda a sua string de formato será reutilizada quantas vezes forem necessárias para manipular todos os argumentos fornecidos. Veja exemplos.

    
por 21.10.2015 / 13:09
0

printf é bom para imprimir octais arbitrários e assim por diante, mas você está apenas tentando inserir ESC - para poder digitá-lo. Se você está preocupado com o seu terminal comendo a fuga, você pode fazer CTRL+V primeiro.

e assim ...

NORM='^[[0m'

... é o que você provavelmente verá na sua tela depois de digitar a seqüência descrita acima, porque o tty irá citar a saída de caracteres não imprimíveis dessa forma, mas o shell irá ler o literal % caractereESC no lugar da sequência de escape ^[ .

Esse recurso é independente de qualquer programa emulador de terminal ou shell ou o que você tiver - embora seja configurável. O kernel do sistema gerencia sua entrada conforme configurado pelo utilitário stty . As configurações padrão - em um BSD, Linux ou (até onde eu sei) quase qualquer outro sistema similar a unix - geralmente incluem a maioria das seguintes opções:

stty --help 2>&1 | sed -e '/Special/,$!d;/./!q'
Special characters:
 * discard CHAR  CHAR will toggle discarding of output
   eof CHAR      CHAR will send an end of file (terminate the input)
   eol CHAR      CHAR will end the line
 * eol2 CHAR     alternate CHAR for ending the line
   erase CHAR    CHAR will erase the last character typed
   intr CHAR     CHAR will send an interrupt signal
   kill CHAR     CHAR will erase the current line
 * lnext CHAR    CHAR will enter the next character quoted
   quit CHAR     CHAR will send a quit signal
 * rprnt CHAR    CHAR will redraw the current line
   start CHAR    CHAR will restart the output after stopping it
   stop CHAR     CHAR will stop the output
   susp CHAR     CHAR will send a terminal stop signal
 * swtch CHAR    CHAR will switch to a different shell layer
 * werase CHAR   CHAR will erase the last word typed

O caractere lnext é aquele tipicamente configurado para CTRL+V . Pode ser bastante útil - você pode fazer CTRL+V então CTRL+J , por exemplo, para inserir uma nova linha no buffer de entrada antes que a concha tenha a chance de Leia-o. Experimente - e você provavelmente notará que não vê o prompt $PS2 do shell como faria caso contrário. Para shells que sequestram a chave TAB , normalmente você pode digitar uma literalmente na linha de comando, prefixando o pressionamento de tecla com CTRL+V . Você pode inserir caracteres literais CTRL+D EOF e CTRL+C INT caracteres (se não usando zsh ) da mesma forma.

A propósito, a maioria desses caracteres nem é especial para shell, e, literalmente, quando inseridos, nem é necessário citar (embora uma nova linha, é claro) .

Faça stty -a para ter uma visão clara de como o seu terminal está configurado - as chances são muito boas, ele é configurado da mesma forma que o meu e todo o resto.

E se você digitar literalmente um caracter ESC em um script de shell enquanto o edita, ele permanecerá no arquivo como está - o caractere literal está lá onde você o coloca toda vez que o script é chamado, e não precisa de expansão ou outra interpretação para fazer com que isso signifique o que deveria.

    
por 20.12.2015 / 10:43