Por que printf é melhor que eco?

547

Ouvi dizer que printfé melhor que echo. Só me lembro de uma instância da minha experiência em que tive que usar printfporque echonão funcionou para inserir algum texto em algum programa do RHEL 5.8, mas printffuncionou. Mas, aparentemente, existem outras diferenças, e eu gostaria de saber o que são e se existem casos específicos em que usar um contra o outro.

anfíbio
fonte
Whab sobre echo -e?
neverMind9 16/06
1
@ neverMind9 echo -enão funciona sh, apenas em bash(por algum motivo), e o docker, por exemplo, usa shpor padrão seus RUNcomandos
Ciprian Tomoiagă
O @ CiprianTomoiagă echo -epara mim funciona no Android Terminal, que é essencialmente sh.
neverMind9 12/07

Respostas:

756

Basicamente, é um problema de portabilidade (e confiabilidade).

Inicialmente, echonão aceitava nenhuma opção e não expandia nada. Tudo o que estava fazendo era apresentar seus argumentos separados por um caractere de espaço e finalizados por um caractere de nova linha.

Agora, alguém pensou que seria bom se pudéssemos fazer coisas como echo "\n\t"gerar nova linha ou caracteres de tabulação ou ter uma opção para não gerar o caractere de nova linha à direita.

Eles então pensaram mais, mas em vez de adicionar essa funcionalidade ao shell (como perlonde aspas duplas, \tna verdade significa um caractere de tabulação), eles o adicionaram echo.

David Korn percebeu o erro e introduziu uma nova forma de citações de shell: $'...'que depois foi copiada bashe zshque era muito tarde para isso.

Agora, quando um UNIX padrão echorecebe um argumento que contém os dois caracteres \e t, em vez de produzi-los, gera um caractere de tabulação. E assim que aparece \cem um argumento, ele para de produzir (portanto, a nova linha à direita também não é produzida).

Outros shells / fornecedores / versões do Unix escolheram fazer de maneira diferente: eles adicionaram uma -eopção para expandir as seqüências de escape e uma -nopção para não gerar a nova linha final. Alguns têm -Eque desativar sequências de escape, outros têm, -nmas -ea lista de seqüências de escape suportadas por uma echoimplementação não é necessariamente a mesma que a suportada por outra.

Sven Mascheck tem uma boa página que mostra a extensão do problema .

Pelos echoimplementações que suportam opções, há geralmente nenhum apoio de um --para marcar o fim de opções (o echobuiltin de algumas conchas não-Bourne-como fazer, e zsh suporta -para que embora), assim, por exemplo, é difícil para a saída "-n"com echono muitas conchas.

Em alguns shells como bash¹ ou ksh93² ou yash( $ECHO_STYLEvariável), o comportamento depende até de como o shell foi compilado ou do ambiente ( echoo comportamento do GNU também mudará se $POSIXLY_CORRECTestiver no ambiente e com a versão 4 , zshcom sua bsd_echoopção, alguns baseados em pdksh com sua posixopção ou se são chamados como shou não). Portanto, dois bash echos, mesmo da mesma versão do, bashnão garantem o mesmo comportamento.

O POSIX diz: se o primeiro argumento é -nou qualquer argumento contém barras invertidas, o comportamento não é especificado . bashO eco nesse sentido não é POSIX, pois, por exemplo, echo -enão está sendo emitido -e<newline>conforme o POSIX exige. A especificação UNIX é mais rígida, proíbe -ne exige a expansão de algumas seqüências de escape, incluindo a \cque interrompe a saída.

Essas especificações não são realmente úteis aqui, pois muitas implementações não são compatíveis. Mesmo alguns sistemas certificados como o macOS 5 não são compatíveis.

Para realmente representar a realidade atual, o POSIX deve realmente dizer : se o primeiro argumento corresponder ao ^-([eEn]*|-help|-version)$regexp estendido ou se algum argumento contiver barras invertidas (ou caracteres cuja codificação contenha a codificação do caractere de barra invertida, como αnos códigos de idioma usando o conjunto de caracteres BIG5), o comportamento será não especificado.

Em suma, você não sabe o echo "$var"que produzirá, a menos que possa ter certeza de que $varnão contém caracteres de barra invertida e não começa com -. A especificação POSIX realmente nos diz para usar printfnesse caso.

Então, o que isso significa é que você não pode usar echopara exibir dados não controlados. Em outras palavras, se você estiver escrevendo um script e estiver recebendo entradas externas (do usuário como argumentos ou nomes de arquivos do sistema de arquivos ...), não poderá usá echo-lo para exibi-lo.

Isso é bom:

echo >&2 Invalid file.

Isso não é:

echo >&2 "Invalid file: $file"

(Embora funcione bem com algumas echoimplementações (não compatíveis com UNIX), como bashas quando a xpg_echoopção não foi ativada de uma maneira ou de outra como no momento da compilação ou através do ambiente).

file=$(echo "$var" | tr ' ' _)não é OK na maioria das implementações (excepções são yashcom ECHO_STYLE=raw(com a ressalva de que yash's variáveis não pode conter seqüências arbitrárias de bytes por isso não nomes de arquivos arbitrários) e zsh' s echo -E - "$var"6 ).

printf, por outro lado, é mais confiável, pelo menos quando está limitado ao uso básico de echo.

printf '%s\n' "$var"

Produzirá o conteúdo de $varseguido por um caractere de nova linha, independentemente de qual caractere ele possa conter.

printf '%s' "$var"

A saída será sem o caractere de nova linha à direita.

Agora, também existem diferenças entre printfimplementações. Há um núcleo de recursos especificado pelo POSIX, mas há muitas extensões. Por exemplo, alguns suportam a %qpara citar os argumentos, mas como isso é feito varia de shell para shell, algum suporte \uxxxxpara caracteres unicode. O comportamento varia printf '%10s\n' "$var"em locais de vários bytes, existem pelo menos três resultados diferentes paraprintf %b '\123'

Mas, no final, se você se ater ao conjunto de recursos do POSIX printfe não tentar fazer algo muito sofisticado com ele, estará sem problemas.

Mas lembre-se de que o primeiro argumento é o formato, portanto, não deve conter dados variáveis ​​/ não controlados.

Um mais confiável echopode ser implementado usando printf, como:

echo() ( # subshell for local scope for $IFS
  IFS=" " # needed for "$*"
  printf '%s\n' "$*"
)

echo_n() (
  IFS=" "
  printf %s "$*"
)

echo_e() (
  IFS=" "
  printf '%b\n' "$*"
)

O subshell (que implica gerar um processo extra na maioria das implementações de shell) pode ser evitado usando-se local IFScom muitos shells ou escrevendo-o como:

echo() {
  if [ "$#" -gt 0 ]; then
     printf %s "$1"
     shift
  fi
  if [ "$#" -gt 0 ]; then
     printf ' %s' "$@"
  fi
  printf '\n'
}

Notas

1. como basho echocomportamento pode ser alterado.

Com bash, no tempo de execução, há duas coisas que controlam o comportamento de echo(ao lado enable -n echoou redefinindo echocomo uma função ou alias): a xpg_echo bashopção e se bashestá no modo posix. posixO modo pode ser ativado se bashfor chamado como shou se POSIXLY_CORRECTestiver no ambiente ou com a posixopção:

Comportamento padrão na maioria dos sistemas:

$ bash -c 'echo -n "\0101"'
\0101% # the % here denotes the absence of newline character

xpg_echo expande seqüências conforme o UNIX exige:

$ BASHOPTS=xpg_echo bash -c 'echo "\0101"'
A

Ainda honra -ne -e(e -E):

$ BASHOPTS=xpg_echo bash -c 'echo -n "\0101"'
A%

Com xpg_echoe modo POSIX:

$ env BASHOPTS=xpg_echo POSIXLY_CORRECT=1 bash -c 'echo -n "\0101"'
-n A
$ env BASHOPTS=xpg_echo sh -c 'echo -n "\0101"' # (where sh is a symlink to bash)
-n A
$ env BASHOPTS=xpg_echo SHELLOPTS=posix bash -c 'echo -n "\0101"'
-n A

Desta vez, bashé compatível com POSIX e UNIX. Observe que, no modo POSIX, bashainda não é compatível com POSIX, pois não é produzido -eem:

$ env SHELLOPTS=posix bash -c 'echo -e'

$

Os valores padrão para xpg_echo e POSIX pode ser definida em tempo de compilação com os --enable-xpg-echo-defaulte --enable-strict-posix-defaultopções para o configurescript. Isso é tipicamente o que as versões recentes do OS / X fazem para criar as suas /bin/sh. No Unix / Linux implementação / distribuição no seu perfeito juízo normalmente faria isso por /bin/bashembora . Na verdade, isso não é verdade, o /bin/bashque o Oracle envia com o Solaris 11 (em um pacote opcional) parece ter sido construído --enable-xpg-echo-default(não era o caso no Solaris 10).

2. Como ksh93o echocomportamento pode ser alterado.

Em ksh93, se echoexpande seqüências de escape ou não e reconhece opções depende do conteúdo das variáveis ​​de ambiente $PATHe / ou $_AST_FEATURES.

Se $PATHcontiver um componente que contenha /5binou /xpgantes do componente /binou /usr/bin, ele se comportará da maneira SysV / UNIX (expande seqüências, não aceita opções). Se encontrar /ucbou /bsdprimeiro ou se $_AST_FEATURES7 contiver UNIVERSE = ucb, ele se comportará da maneira BSD 3 ( -epara ativar a expansão, reconhece -n).

O padrão é dependente do sistema, BSD no Debian (veja a saída de builtin getconf; getconf UNIVERSEnas versões recentes do ksh93):

$ ksh93 -c 'echo -n' # default -> BSD (on Debian)
$ PATH=/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /xpg before /bin or /usr/bin -> XPG
-n
$ PATH=/5binary:$PATH ksh93 -c 'echo -n' # /5bin before /bin or /usr/bin -> XPG
-n
$ PATH=/5binary:$PATH _AST_FEATURES='UNIVERSE = ucb' ksh93 -c 'echo -n' # -> BSD
$ PATH=/ucb:/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /ucb first -> BSD
$ PATH=/bin:/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /bin before /xpg -> default -> BSD

3. BSD para eco -e?

A referência ao BSD para o manuseio da -eopção é um pouco enganosa aqui. A maioria desses echocomportamentos diferentes e incompatíveis foram todos introduzidos na AT&T:

  • \n, \0ooo, \cNo Banco do programador Trabalho UNIX (baseado em Unix V6), eo restante ( \b, \r...) em Unix System III Ref .
  • -nno Unix V7 (por Dennis Ritchie Ref )
  • -eno Unix V8 (por Dennis Ritchie Ref )
  • -Epossivelmente veio inicialmente bash(CWRU / CWRU.chlog na versão 1.13.5 menciona Brian Fox adicionando-o em 18/10/1992, GNU echocopiando-o logo depois no sh-utils-1.8 lançado 10 dias depois)

Embora o echobuilt-in do shou BSDs tenha suportado -edesde o dia em que começaram a usar o shell Almquist no início dos anos 90, o echoutilitário independente até hoje não o suporta por lá (o FreeBSDecho ainda não suporta -e, embora suporte -ncomo Unix V7 (e também \cmas apenas no final do último argumento)).

A manipulação de -efoi adicionada a ksh93s echono universo BSD na versão ksh93r lançada em 2006 e pode ser desativada no momento da compilação.

4. Mudança de comportamento do GNU eco em 8.31

Desde coreutils 8.31 (e este cometem ), GNU echoagora expande seqüências de escape por padrão quando POSIXLY_CORRECT está no ambiente, para coincidir com o comportamento de bash -o posix -O xpg_echo's echoincorporado (ver relatório de erro ).

5. macOS echo

A maioria das versões do macOS recebeu certificação UNIX do OpenGroup .

Seu shembutido echoé compatível, pois é bash(uma versão muito antiga) criada com xpg_echoativado por padrão, mas seu echoutilitário independente não é. env echo -nnão produz nada em vez de -n<newline>, env echo '\n'gera em \n<newline>vez de <newline><newline>.

Essa /bin/echoé a do FreeBSD que suprime a saída de nova linha se o primeiro argumento for -nou (desde 1995) se o último argumento terminar \c, mas não suporta nenhuma outra sequência de barra invertida exigida pelo UNIX, nem mesmo \\.

6. echoimplementações que podem gerar dados arbitrários literalmente

Estritamente falando, você também pode contar que o FreeBSD / macOS /bin/echoacima (não o shell deles echo), onde zshs echo -E - "$var"ou yash's ECHO_STYLE=raw echo "$var"( printf '%s\n' "$var") podem ser escritos:

/bin/echo "$var
\c"

As implementações suportadas -Ee -n(ou podem ser configuradas para) também podem:

echo -nE "$var
"

E zsh's echo -nE - "$var"( printf %s "$var") poderia ser escrito

/bin/echo "$var\c"

7. _AST_FEATURESe o ASTUNIVERSE

Ele _AST_FEATURESnão deve ser manipulado diretamente, é usado para propagar as definições de configuração AST pela execução do comando. A configuração deve ser feita através da astgetconf()API (não documentada) . Por dentro ksh93, o getconfbuilt-in (ativado com builtin getconfou chamando command /opt/ast/bin/getconf) é a interface paraastgetconf()

Por exemplo, você faria builtin getconf; getconf UNIVERSE = attpara alterar a UNIVERSEconfiguração para att(causando o echocomportamento do SysV entre outras coisas). Depois de fazer isso, você notará que a $_AST_FEATURESvariável de ambiente contém UNIVERSE = att.

Stéphane Chazelas
fonte
13
Muitos desenvolvimentos iniciais do unix ocorreram isoladamente, e bons princípios de engenharia de software como 'quando você altera a interface, altera o nome' não foram aplicados.
Henk Langeveld 14/03
7
Como observação, a única (e talvez única) vantagem de echoexpandir as \xseqüências em oposição ao shell como parte da sintaxe de citação é que você pode gerar um byte NUL (outro design indiscutivelmente incorreto do Unix são aqueles delimitados por nulos cordas, onde metade das chamadas de sistema (como execve()) não podem tomar seqüências arbitrárias de bytes)
Stéphane Chazelas
Como você pode ver na página da Web de Sven Maschek, até printfparece ser um problema, já que a maioria das implementações faz isso errado. A única implementação correta que eu conheço está boshe a página de Sven Maschek não lista os problemas com bytes nulos via \0.
schily
1
Esta é uma ótima resposta - obrigado @ StéphaneChazelas por escrevê-la.
JoshuaRLi
28

Você pode querer usar printfpara suas opções de formatação. echoé útil quando se trata de imprimir o valor de uma variável ou de uma linha (simples), mas é tudo o que existe. printfpode basicamente fazer o que a versão C pode fazer.

Exemplo de uso e recursos:

Echo:

echo "*** Backup shell script ***"
echo
echo "Runtime: $(date) @ $(hostname)"
echo

printf:

vech="bike"
printf "%s\n" "$vech"

Fontes:

NlightNFotis
fonte
@ 0xC0000022L Estou corrigido, obrigado. Não percebi que estava vinculado ao site errado na minha pressa de responder à pergunta. Obrigado por sua contribuição e correção.
NLNFotis
7
O uso echopara imprimir uma variável pode falhar se o valor da variável contiver metacaracteres.
Keith Thompson
17

Uma "vantagem", se você quiser chamar assim, seria não precisar dizer que gostaria echode interpretar certas seqüências de escape, como \n. Ele sabe interpretá-los e não precisará -edisso.

printf "some\nmulti-lined\ntext\n"

(Nota: o último \né necessário, echoimplica, a menos que você dê a -nopção)

versus

echo -e "some\nmulti-lined\ntext"

Observe a última \nem printf. No final do dia, é uma questão de gosto e requisitos o que você usa: echoou printf.

0xC0000022L
fonte
1
Verdadeiro /usr/bin/echoe bashembutido. O dash, kshe zshbuiltin echonão precisam ser -ealternados para expandir caracteres com escape de barra invertida.
manatwork 23/02
Por que as citações de susto? Sua redação implica que não é necessariamente uma vantagem real.
Keith Thompson
2
@ KeithThompson: na verdade, tudo o que eles querem dizer é que nem todo mundo pode considerá-lo uma vantagem.
0xC0000022L
Você poderia expandir isso? Por que não seria uma vantagem? A frase "se você quiser chamar assim" implica fortemente que você acha que não é.
Keith Thompson
2
Ou você poderia simplesmente fazer printf '%s\n' 'foo\bar.
precisa saber é o seguinte
6

Uma desvantagem printfé o desempenho, porque o shell interno echoé muito mais rápido. Isso ocorre principalmente no Cygwin, onde cada instância de um novo comando causa uma sobrecarga pesada do Windows. Quando mudei meu programa de eco pesado de usar /bin/echopara eco do shell, o desempenho quase dobrou. É uma troca entre portabilidade e desempenho. Não é um slam dunk para sempre usar printf.

John
fonte
15
printfatualmente é construído na maioria dos shells (bash, dash, ksh, zsh, yash, alguns derivados do pdksh ... então inclui os shells normalmente encontrados no cygwin). As únicas exceções notáveis ​​são alguns pdkshderivativos.
Stéphane Chazelas
Mas muitas dessas implementações de impressão estão quebradas. Isso é essencial quando você gosta de usar printfpara gerar nul bytes, mas algumas implementações interpretam `\ c 'na string de formato, mesmo que não devam.
schily
2
@schily, o comportamento de printfnão é especificado pelo POSIX se você usar \ca string format, para que as printfimplementações possam fazer o que quiserem nesse sentido. Por exemplo, alguns tratam da mesma forma que no PWB echo(faz com que printf saia), o ksh o usa para \cAcaracteres de controle (para o argumento do formato printf e para $'...'mas não para echonem print!). Não tenho certeza o que isso tem a ver com a impressão de bytes NUL, ou talvez você está se referindo a ksh's printf '\c@'?
Stéphane Chazelas