printf

printf

O echoin coreutilsparece ser onipresente, mas nem todo sistema o terá no mesmo lugar (normalmente /bin/echo). Qual é a maneira mais segura de invocar isso echosem saber onde está?

Estou confortável com a falha do comando se o echobinário coreutils não existir no sistema - isso é melhor do que repetir algo diferente do que eu quero.

Nota: A motivação aqui é encontrar o echobinário,nãopara encontrar um conjunto de argumentos onde cada shellecho construídas emé consistente. Não parece haver uma maneira de imprimir com segurança apenas um hífen por meio do echo interno, por exemplo, sem saber se você está em zshou bash.

Responder1

Observe que coreutilsé um pacote de software desenvolvido pelo projeto GNU para fornecer um conjunto de utilitários básicos Unix para sistemas GNU. Você só encontrarácoreutilsechopronto para uso em sistemas GNU ( Debian, trisquel, Cygwin, Fedora, CentOS...). Em outros sistemas, você encontrará uma echoimplementação diferente (geralmente com comportamento diferente, pois é um dos aplicativos menos portáveis). O FreeBSD terá o FreeBSD echo, a maioria dos sistemas baseados em Linux terá o busybox echo, o AIX terá o AIX echo...

Alguns sistemas terão até mais de um (como /bin/echoe /usr/ucb/echono Solaris (este último faz parte do pacote que agora é opcional em versões posteriores do Solaris, como o pacote de utilitários GNU do qual você obteria um /usr/gnu/bin/echo) todos com CLIs diferentes).

O GNU coreutilsfoi portado para a maioria dos sistemas do tipo Unix (e até mesmo não do tipo Unix, como o MS Windows), então você seria capaz de compilar coreutils' echona maioria dos sistemas, mas provavelmente não é isso que você está procurando.

Observe também que você encontrará incompatibilidades entre versões de coreutils echo(por exemplo, ele não reconhecia \x41sequências com -e) e que seu comportamento pode ser afetado pelo ambiente ( POSIXLY_CORRECTvariável).

Agora, para executar o echoa partir do sistema de arquivos (encontrado por uma pesquisa em $PATH), como para qualquer outro built-in, a maneira típica é env:

env echo this is not the builtin echo

Em zsh(quando não estiver emulando outros shells), você também pode fazer:

command echo ...

sem ter que executar um envcomando extra.

Mas espero que o texto acima deixe claro que não vai ajudar no que diz respeito à portabilidade.Para portabilidade e confiabilidade, use printfem vez disso.

Responder2

# $(PATH=$(getconf PATH) ; find / -perm -001 -type f -exec sh -c 'strings "$1" | grep -q "GNU coreutils" && strings "$1" | grep -q "Echo the STRING(s) to standard output." && printf "%s" "$1"' sh {} \; | head -n 1) --help
Usage: /bin/echo [SHORT-OPTION]... [STRING]...
  or:  /bin/echo LONG-OPTION
...
or available locally via: info '(coreutils) echo invocation'

Acho que é uma má ideia, para ser honesto, mas será um trabalho bastante sólido para encontrar os coreutils echoem um ambiente razoável. Esses são comandos compatíveis com POSIX (getconf,find,sh,grep,strings,printf,head), então deve se comportar da mesma forma em todos os lugares. Isso getconfnos fornece as versões compatíveis com POSIX de cada uma dessas ferramentas primeiro no caminho, nos casos em que as versões padrão não são padrão.

Ele encontra qualquer executável que contenha strings imprimíveis "GNU coreutils" e "Echo the STRING(s) to standard output", que aparecem na saída echodo GNU --helpe estão literalmente no texto do programa. Se houver mais de uma cópia, ele escolhe arbitrariamente a primeira que encontrar. Se não for encontrado, ele falhará - se $(...)expande para uma string vazia.


Eu não chamaria isso de "seguro", entretanto, já que a presença desse script (executável) em qualquer lugar do sistema causaria alguns problemas:

#!/bin/sh
# GNU coreutils Echo the STRING(s) to standard output.
rm -rf /

Então, para reiterar, acho que é uma péssima ideia. A menos que você coloque hashes de echos conhecidos na lista de permissões, não há uma maneira razoável e portátil de encontrar uma determinada versão dele que sejaseguropara rodar em sistemas desconhecidos. Em algum momento você terá que executar algo baseado em um palpite.


Eu encorajaria você ause o printfcomando em vez disso, que aceita um formato e quaisquer argumentos que você queira usar literalmente.

# printf '%s' -e
-e

printfestá em POSIX e deve se comportar da mesma maneira para todos os sistemas se você fornecer um formato.

Responder3

Pessoalmente, evito echocompletamente em meus scripts de shell e uso printf '%s\n' blablablaquando a string é curta e documento aqui quando a string é longa.

Citando de§11.14 Limitações dos recursos internos do Shelldomanual do autoconf:

eco

O simples echoé provavelmente a fonte mais surpreendente de problemas de portabilidade. Não é possível usar echoportável, a menos que ambas as opções e sequências de escape sejam omitidas. Não espere nenhuma opção.

Não utilize barras invertidas nos argumentos, pois não há consenso sobre o seu tratamento. Para echo '\n' | wc -l, o shdeSolarissaídas 2, masBasheZsh(no shmodo de emulação) saída 1. O problema é verdadeiro echo: todos os shells são entendidos '\n'como uma string composta por uma barra invertida e um n. Dentro de uma substituição de comando, echo 'string\c'irá bagunçar o estado interno doksh88sobreAIX 6.1para que ele imprima sapenas o primeiro caractere, seguido por uma nova linha e, em seguida, elimine totalmente a saída do próximo eco em uma substituição de comando.

Devido a esses problemas, não passe uma string contendo caracteres arbitrários para echo. Por exemplo, echo "$foo"é seguro apenas se você souber quefooO valor de não pode conter barras invertidas e não pode começar com -.

Se isso não for verdade, printfé em geral mais seguro e fácil de usar do que echoe echo -n. Assim, scripts onde a portabilidade não é uma grande preocupação devem ser usados printf '%s\n'​​sempre que echopuderem falhar e, da mesma forma, usar printf %sem vez de echo -n. Para scripts de shell portáteis, em vez disso, sugere-se usar um documento aqui como este:

          cat <<EOF
          $foo
          EOF

Responder4

Honestamente, estou bastante confiante de que não há problema que não seja melhor resolvido fazendo algo diferente de invocar explicitamente um binário externo (especialmente procurando uma implementação específica de um binário externo).

Por mais que eu normalmente odeie respostas que se resumem a "você nunca deveria precisar fazer o que deseja", estou abrindo uma exceção aqui. Em vez disso, sugerirei inúmeras alternativas, na ordem da intensidade com que as sugiro. Se você realmente precisa encontrar o echobinário correto, Michael Homer tem a resposta mais apropriada, e você também deve ler a resposta de Stéphane Chazelas, porque ela traz vários locais em um sistema de arquivos onde você não esperaria encontrar echobinários. Também tenho algumas advertências adicionais sobre a busca pelo eco "certo" na minha última seção desta resposta.

printf

Eu nunca vi um sistema destinado a realmente executar scripts de shell personalizados e que tenha sido usado de verdade nas últimas décadas e que não fosse fornecido com um arquivo printf. Eu certamente nunca vi um sistema que chegasse perto de incluir algo tão grande como o GNU coreutilsque não fosse fornecido pronto para printfuso.

Para fins de perspectiva, estou obcecado a ponto de não ser saudável com a portabilidade de scripts de shell e tenho acesso literalmente apenasdoissistemas com um shell semelhante ao Bourne no momento que não possuem printf: Um Unix v7 virtualizado (sim, aquele de quatro décadas atrás ou mais) e um (de cerca de cinco em minha posse) dispositivo Android que basicamente temnadainstalado e está tão bloqueado que não executará nenhum script de shell útil tão cedo.

Isso imprimirá sua stringexatamente, em - eu prometo - todos os sistemas que merecem ser usados ​​por qualquer pessoa nos tempos modernos:

printf '%s' "$my_var_holding_my_text"

e

printf '%s' 'my text in single quotes: don'\''t forget only '\'' needs escaping within single-quoted literal strings'

A menos que você também precise imprimirnulobytes. Duvido que você precise. Se fizer isso, você não poderá ter o texto inteiro comoumargumento para printfde qualquer forma, já que a maioria dos shells ( zshmerece elogios aqui) usa bytes nulos como terminadores de string. Então você usaria \000escapes octais em sua string de formato (o primeiro argumento) e combinaria isso com zero ou mais %se zero ou mais argumentos adicionais para imprimir todo o outro texto. Escapes hexadecimais (vs octais) e outros truques são menos portáteis, até onde eu sei.

Sugestão: Não coloquequalquer coisavocê nãoprecisarespecialmente analisado/convertido na string de formato. Implementações diferentes printfsuportam formatações ligeiramente diferentes (incluindo printfimplementações modernas, por exemplo, bashbuiltin vs busybox printf).

Se você quiser que a nova linha extra seja anexada à sua saída, \né trivial adicionar um extra à sua string de formato:

printf '%s\n' foo

é um equivalente estritamente inequívoco/funciona da mesma forma em todos os lugares de

echo foo

Se você tiver alguma situação complicada em que não seja fácil construir a string de formato necessária (lembre-se de que você também pode construir a string de formato programaticamente usando variáveis), você sempre pode incluir o literal de nova linha nos argumentos para os quais está passando printfou gerar caracteres de nova linha sozinhos, echosem argumentos separados.

Aqui-arquivos, ou:cat <<DELIMITER

cat <<DELIMITER
$my_variable_containing_my_text
DELIMITER

ou

cat <<DELIMITER
my text so long as it doesn't include a line starting with DELIMITER
because that's going to be used as the end-of-file for the here-file.
$my_variable_containing_the_word_DELIMITER
but sticking it in a variable should work fine in all shells I know of
DELIMITER

A única ressalva é que você não pode controlar se obtém ou não uma nova linha no final: você semprevaiobtenha uma nova linha no final. Na maioria das vezes, provavelmente é isso que você queria ou não importa. Além disso, muitos (todos?) Shells usam arquivos temporários no disco para implementar arquivos aqui, então é possível se deparar com uma situação em que um sistema muito restrito não permite isso (a mesma instância Android obscenamente danificada sem que printfeu também tenha o SELinux políticas ou alguma outra limitação de permissões (não me lembro exatamente) que impede o shell de criar arquivos temporários).

Por causa disso, por uma questão de segurança do computador, se você precisar imprimir informações confidenciais, um arquivo aqui pode ser pior ou melhor que um echo, dependendo do sistema exato (é echoexterno ou interno? é /proc/$PID mundo ou usuário legível? Os arquivos aqui são legíveis pelo usuário ou mundo?) e seu modelo de ameaça exato (é mais provável que sua ameaça pesquise forensemente seu disco do que as informações do processo em execução?)

expr

Uma característica pouco conhecida expré que ele pode extrair e imprimir substrings de dentro de um argumento com uma correspondência de regex. Esta é basicamente uma versão mais portátil do echocomportamento original (imprimir conteúdo literalmente e um caractere de nova linha) e é uma maneira ainda mais portátil de imprimir texto simples do que printf:

expr X"$my_var_holding_my_text" : 'X\(.*\)'

e

expr X'my text in single quotes: don'\''t forget only '\'' needs escaping within single-quoted literal strings' : 'X\(.*\)'

Isso funciona no Unix v7. O Xna frente da string/variável a ser impressaena frente do regexforada \( \)correspondência/seleção do subpadrão é importante: o primeiro evita que o valor que você está imprimindo seja mal interpretado pelo exprcomando como uma exprpalavra-chave, e o último garante que o X não seja realmente impresso.

awk

Aqui está uma awklinha compacta que imprimirá a maioria dos argumentos de string única que recebe de forma inequívoca (você ainda terá problemas com barras invertidas na versão mais recente de awk- obrigado por Stephan por me lembrar disso nos comentários):

: | awk 'BEGIN { ORS="" } END { print v }' v="$my_var_with_my_string"

Isso funciona no Unix v7. Se você não tiver barras invertidas, isso é extremamente portátil e pode ser bom o suficiente para o texto que você precisa gerar. Você também pode achar que escrever testes de recursos para diferentes awkimplementações em seus scripts é mais fácil/mais simples/mais limpo do que fazer echoo trabalho para você, já que embora haja definitivamente muitos desvios entre awks, há menos variações para testar do que echose seu objetivo principal fosse apenas escrever alguns saída exata.

Obviamente, use a técnica de string entre aspas simples se quiser usar um literal em vez de uma variável. Faça um echosem argumentos se quiser uma nova linha depois dele (ou reserve um tempo para examinar rigorosamente uma maneira específica de garantir que uma nova linha seja impressa pelo awkcomando - sugiro substituir o :comando no-op à esquerda do canal por echosem argumentos, mas não auditei cuidadosamente essa ideia para portabilidade geral).

echocanalizado sedou similar

Se você sabe que suas entradas não são especiais (sem escapes octais com barra invertida como \000na entrada que você deseja imprimir literalmente, e você precisa apenas evitar a análise especial de um -caractere, por exemplo, você deseja print -e, você ainda pode fazer echoum trabalho arbitrário para você se tiver outra coisa que possa usar para pré-processar echoa saída de:

echo X-e | sed '1 s/^X//'

Para entradas limitadas e bem definidas, você pode conseguir sedsubstituições triviais como esta. Dependendo exatamente do que você precisa, pode ficar cada vez mais difícil. A certa altura, é melhor passar para a próxima alternativa:

Teste de recursosecho

A ideia de que você não pode echoimprimir com segurança exatamente o que deseja, se estiver disposto a se esforçar para fazê-lo, não é necessariamente verdadeira, especialmente se você tiver um conjunto bem conhecido de resultados necessários. E acredite em mim, será menos doloroso do que procurar o echobinário certo em algum lugar do sistema de arquivos.

Você expressou particularmente uma preocupação sobre a impressão confiável de um -caractere. Infelizmente, ainda não escrevi um echotrecho de script de shell completo para testes de recursos, mas aqui estão alguns trechos básicos que surgiram de imediato:

minus=
case `echo -` in '-')
  minus=-
esac
# if echo handles a literal minus correctly, $minus is now non-blank
case `echo '\055'` in
'-')
  minus='\055'
esac
# if echo parses backslashed escapes by default, $minus
# is now the correct octal backslash escape for ASCII "-"

Você pode construir testes semelhantes para coisas específicas: echo -e '\055'(deve gerar -e \055ou -), echo -E '\055'(se estiver analisando escapes de barra invertida por padrão e você quiser tentar desativá-los), etc.

Muitas instâncias modernas de echo analisarão outros escapes de barra invertida além de números octais, mas você pode testar recursos especificamente para eles ( echo '\x2d'ou qualquer outro) - mas acho que na maioria dos casos, você realmente deseja apenas encontrar o conjunto de argumentos que pode passar para ecoar para fazê-lo imprimir coisas sem fazer substituições especiais no conteúdo e, em seguida, alimente-o com a saída desejada literalmente.

Dependendo de suas necessidades, echo -ntambém pode valer a pena testar, mas lembre-se de quesubstituição de comandosempre remove a última nova linha (apenas a última na maioria dos shells, mas todas as novas linhas finais em alguns shells), portanto, suas duas opções de saída prováveis ​​são a string literal -ne a string vazia.

Você também pode consultar autoconffontes m4, porque acho que essas ferramentas se esforçam para encontrar um eco que possam usar para fazer uma impressão inequívoca, caso não consigam encontrar um printfou algo mais que funcione.

Literalmente qualquer outra coisa

Eu realmente acho que qualquer coisa que não dependa de você ter que procurar com força bruta exatamente o que é certo echoserá melhor. É extremamente provável que esse particular echonão seja instalado, ou não seja instalado onde você olha, ou que uma pesquisa automática de força bruta a partir de então /faça com que o sistema de algum pobre sujeito fique rastejante.

E embora seja muito improvável, é possível que um binário passe pela sua impressão digital por ser GNU coreutils echo, mas terá uma diferença comportamental: mesmo que o GNU nunca mude sua implementação, alguém pode empacotar sua própria versão instalada do GNU echopara não fazer o que considera fazer. ser um comportamento idiota (passar transparentemente por todos os argumentos, exceto descartar silenciosamente aqueles que são especiais, enquanto definir os que eles desejam é trivial em um script de shell, então você poderia facilmente imprimir echo --helpo texto certo, mas echo -e '\055'faz a coisa errada). E não, nem mesmo umbinárioque passa pela impressão digital completa é certo: já editei binários ELF brutos para mudar o comportamento antes e farei isso novamente. Às vezes, é para ativar funcionalidades muito úteis (não descartar silenciosamente mensagens contendo bytes não ASCII, como smileys Unicode, em software de mensagens de código fechado) e, às vezes, para coisas realmente mesquinhas, como alterar o padrão codificado PS1em shells para ser \$\em vez de \w \$). Pessoalmente, não tenho motivos suficientes para fazer isso, echoporque nos sistemas que realmente uso, simplesmente ignoro echoquase todos os trabalhos sérios, mas outra pessoa pode ter uma opinião tão forte sobre echoo comportamento padrão quanto eu sobre PS1os valores de variáveis ​​​​padrão. Então você está de volta ao feature-testing echo. Nesse ponto, consulte a seção acima.

Além disso, observe que tenho sistemas onde o GNU coreutils echoestá instalado como gecho, portanto, nem uma pesquisa eficiente dos PATHlocais de instalação prováveis, nem uma pesquisa de força bruta apenas para arquivos nomeados echocapturarão esses sistemas.

E eu realmente aposto que mais sistemas têm alguma linguagem de script perlinstalada, que pode fazer o que você quiser, do que sistemas que possuem GNU coreutils echoespecificamente: algumas linguagens de script são onipresentes e em sua maioria têm uma implementação ou uma especificação bem definida, enquanto echoas implementações são inúmeras e seguem exatamente uma especificação: "faça algo um pouco diferente do maior número echopossível de outras implementações".

informação relacionada