Qual é o benefício de usar $ () em vez de backticks em scripts de shell?

175

Há duas maneiras de capturar a saída da linha de comando bash:

  1. Backticks de shell Bourne herdados ``:

    var=`command`
  2. $() sintaxe (que, tanto quanto eu sei, é específica do Bash, ou pelo menos não é suportada por shells antigos não POSIX como o Bourne original)

    var=$(command)

Existe algum benefício em usar a segunda sintaxe em comparação com os backticks? Ou os dois são totalmente 100% equivalentes?

DVK
fonte
14
$()é POSIX e é suportado por todos os shells Bourne modernos, por exemplo, ksh, bash, ash, dash, zsh, busybox, você escolhe. (Um não tão moderno é o Solaris /bin/sh, mas no Solaris você deve usar o moderno /usr/xpg4/bin/sh).
Jens
27
Além disso, uma observação sobre o uso $()e backticks em aliases. Se você tiver o alias foo=$(command)seu .bashrc, commandserá executado quando o próprio comando alias for executado durante a .bashrcinterpretação. Com alias foo=`command`, commandserá executado sempre que o alias for executado. Mas se você escapar do $com o $()formulário (por exemplo alias foo=\$(command)), ele também será executado toda vez que o alias for executado, em vez de durante a .bashrcinterpretação. Tanto quanto eu posso dizer por testes, de qualquer maneira; Não consigo encontrar nada nos documentos do bash que expliquem esse comportamento.
dirtside
4
@dirtside Qual shell é esse, eu testei o bash e o shell POSIX, o backtick é executado quando eu fonte. Exemplo simples: alias curDate = `date` Depois que eu o origino e executo curDate, recebo a mensagem de que ele não consegue encontrar o comando Mon (Origem na segunda-feira), por exemplo.
Thecarpy
@dirtside Não é verdade. Mesmo com o alias, foo = `command` commandé executado apenas uma vez. Eu verifiquei: function aaa () {printf date; eco aaa >> ~ / test.txt; } alias test1 = aaa. A função aaa está executando apenas uma vez (após cada login), não importa quantas vezes o alias ( test1) foi executado. Eu usei .bashrc (no Debian 10).
vitaliydev
Não faço ideia, era a versão do bash que eu estava usando há cinco anos e meio, o que provavelmente era bem antigo até aquele momento. É possível que meus testes estejam incorretos, mas isso pouco importa agora, porque duvido que alguém ainda esteja usando a versão anterior.
dirtside 19/09/19

Respostas:

156

O principal é a capacidade de aninhar os comandos dentro de comandos, sem perder sua sanidade mental, tentando descobrir se alguma forma de fuga funcionará nos retângulos.

Um exemplo, embora um tanto artificial:

deps=$(find /dir -name $(ls -1tr 201112[0-9][0-9]*.txt | tail -1l) -print)

que fornecerá uma lista de todos os arquivos na /dirárvore de diretórios com o mesmo nome do arquivo de texto com data mais antiga de dezembro de 2011 (a) .

Outro exemplo seria algo como obter o nome (não o caminho completo) do diretório pai:

pax> cd /home/pax/xyzzy/plugh
pax> parent=$(basename $(dirname $PWD))
pax> echo $parent
xyzzy

(a) Agora que esse comando específico pode não funcionar realmente, não testei a funcionalidade. Portanto, se você me rejeita, perde de vista a intenção :-) É apenas uma ilustração de como você pode aninhar, não como um trecho pronto para a produção sem erros.

paxdiablo
fonte
86
Espero que todo o código no SO seja snippets prontos para produção, projetados para os padrões de confiabilidade do código shuttle da NASA. Qualquer coisa menos recebe uma bandeira e um voto de exclusão.
DVK
1
@DVK No caso de você não estar brincando, discordo que as contribuições de código devem ser sinalizadas por não serem automaticamente retiradas dos padrões de SO (licenças CC-BY-SA ou MIT), a fim de permitir tais garantias ou adequação à finalidade. Eu, ao invés, a reutilização de código no SO no meu próprio risco e contribuições voto de acordo com a utilidade, méritos técnicos, etc.
chrstphrchvz
9
@chrstphrchvz, se você olhar no meu perfil, verá este pequeno trecho: "Todo o código que eu publico no Stack Overflow é coberto pela licença" Faça o que você quiser com ele ", cujo texto completo é: Do whatever the heck you want with it." :-) De qualquer forma, tenho certeza de que foi humor do DVK.
21417
1
mas e se fosse "cd / home / pax / xyzzy / plover"? Você se encontraria em um labirinto de pequenas passagens sinuosas, todas diferentes?
Duncan
@wchargin você sabia que esse incidente foi o principal motivador da adição de literais definidos usados ​​à linguagem C ++? pt.cppreference.com/w/cpp/language/user_literal #
ThomasMcLeod
59

Suponha que você queira encontrar o diretório lib correspondente a onde gccestá instalado. Você tem uma escolha:

libdir=$(dirname $(dirname $(which gcc)))/lib

libdir=`dirname \`dirname \\\`which gcc\\\`\``/lib

O primeiro é mais fácil que o segundo - use o primeiro.

Jonathan Leffler
fonte
4
Seria bom ver algumas citações em torno dessas substituições de comando!
21417 Tom Tomech
1
Pelo menos para o bash, o comentário de @TomFenech não se aplica a tarefas. x=$(f); x=`f` se comportam da mesma forma que x="$(f)"; x="`f`". Em contraste, a atribuição de matriz x=($(f)); x=(`f`) fazer executar divisão em $IFScaracteres como esperado quando invocando comandos. Isso é conveniente ( x=1 2 3 4não faz sentido), mas inconsistente.
Kdb 05/09/19
@kdb você está certo sobre o x=$(f)trabalho sem aspas. Eu deveria ter sido mais específico; Eu estava propondo usar libdir=$(dirname "$(dirname "$(which gcc)")")/lib(aspas em torno das substituições de comandos internos ). Se não for indicado, você ainda estará sujeito à divisão usual da palavra e à expansão glob.
Tom Fenech
38

Os backticks ( `...`) são a sintaxe herdada exigida apenas pelos bourne-shells mais antigos e não compatíveis com $(...)POSIX, sendo POSIX e mais preferido por vários motivos:

  • As barras invertidas ( \) dentro dos backticks são tratadas de maneira não óbvia:

    $ echo "`echo \\a`" "$(echo \\a)"
    a \a
    $ echo "`echo \\\\a`" "$(echo \\\\a)"
    \a \\a
    # Note that this is true for *single quotes* too!
    $ foo=`echo '\\'`; bar=$(echo '\\'); echo "foo is $foo, bar is $bar" 
    foo is \, bar is \\
  • A citação aninhada no interior $()é muito mais conveniente:

    echo "x is $(sed ... <<<"$y")"

    ao invés de:

    echo "x is `sed ... <<<\"$y\"`"

    ou escrevendo algo como:

    IPs_inna_string=`awk "/\`cat /etc/myname\`/"'{print $1}' /etc/hosts`

    Porque $() usa um contexto totalmente novo para citar

    o que não é portátil, pois as conchas de Bourne e Korn exigiriam essas barras invertidas, enquanto Bash e dash não.

  • A sintaxe para aninhar substituições de comandos é mais fácil:

    x=$(grep "$(dirname "$path")" file)

    do que:

    x=`grep "\`dirname \"$path\"\`" file`

    Porque $() impõe um contexto inteiramente novo para citação, cada substituição de comando é protegida e pode ser tratada por si só, sem preocupação especial com a citação e escape. Ao usar as costas, fica cada vez mais feio depois de dois e acima dos níveis.

    Mais alguns exemplos:

    echo `echo `ls``      # INCORRECT
    echo `echo \`ls\``    # CORRECT
    echo $(echo $(ls))    # CORRECT
  • Ele resolve um problema de comportamento inconsistente ao usar aspas:

    • echo '\$x' saídas \$x
    • echo `echo '\$x'` saídas $x
    • echo $(echo '\$x') saídas \$x
  • A sintaxe dos backticks possui restrições históricas no conteúdo do comando incorporado e não pode manipular alguns scripts válidos que incluem aspas, enquanto o $()formulário mais recente pode processar qualquer tipo de script incorporado válido.

    Por exemplo, esses scripts incorporados válidos de outra forma não funcionam na coluna da esquerda, mas no IEEE da direita :

    echo `                         echo $(
    cat <<\eof                     cat <<\eof
    a here-doc with `              a here-doc with )
    eof                            eof
    `                              )
    
    
    echo `                         echo $(
    echo abc # a comment with `    echo abc # a comment with )
    `                              )
    
    
    echo `                         echo $(
    echo '`'                       echo ')'
    `                              )

Portanto, a sintaxe da substituição de comando$ pré-prefixada deve ser o método preferido, porque é visualmente clara com sintaxe limpa (melhora a legibilidade humana e da máquina), é aninhada e intuitiva, sua análise interna é separada e também é mais consistente (com todas as outras expansões analisadas entre aspas duplas), onde os backticks são a única exceção e o caractere é facilmente camuflado quando adjacente a`" tornando a leitura ainda mais difícil, especialmente com fontes pequenas ou incomuns.

Fonte: Por que o $(...)preferido é sobre `...`(backticks)?no BashFAQ

Veja também:

kenorb
fonte
1
A citação aninhada na substituição de estilo gravis com sotaque é realmente indefinida; você pode usar aspas duplas , externa ou interna, mas não ambas, de maneira portável. Os reservatórios os interpretam de maneira diferente; alguns exigem uma barra invertida para escapar deles, outros exigem que eles não sejam escapados pela barra invertida.
mirabilos
23

De man bash:

          $(command)
   or
          `command`

   Bash performs the expansion by executing command and replacing the com-
   mand  substitution  with  the  standard output of the command, with any
   trailing newlines deleted.  Embedded newlines are not deleted, but they
   may  be  removed during word splitting.  The command substitution $(cat
   file) can be replaced by the equivalent but faster $(< file).

   When the old-style backquote form of substitution  is  used,  backslash
   retains  its  literal  meaning except when followed by $, `, or \.  The
   first backquote not preceded by a backslash terminates the command sub-
   stitution.   When using the $(command) form, all characters between the
   parentheses make up the command; none are treated specially.
dgw
fonte
9

Além das outras respostas,

$(...)

se destaca visualmente melhor do que

`...`

Backticks parecem muito com apóstrofos; isso varia dependendo da fonte que você está usando.

(E, como acabei de notar, os backticks são muito mais difíceis de inserir em exemplos de código embutido.)

Keith Thompson
fonte
2
você deve ter um teclado estranho (ou eu tenho?). Para mim, é muito mais fácil digitar backticks - eles são a tecla do canto superior esquerdo, sem necessidade de SHIFT ou ALT.
DVK 26/02
1
@ DVD: Eu estava falando sobre a aparência deles, não a facilidade de digitação. (Meu teclado provavelmente é o mesmo que o seu.) Ainda assim, agora que você mencionou, acho que tenho melhor memória muscular para $ (e )do que para backtick; YMMV.
26412 Keith Thompson
Nunca programado em bash (pulado de ksh velho para Perl) assim definitivamente nenhuma memória para essa sintaxe específica :)
DVK
1
@DVK, eu pensei que Keith estava se referindo ao fato de que o código que não é de bloco aqui (código de bloco significa usar quatro espaços no início da linha) usa backticks para indicá-lo, dificultando a inserção de backticks neles, outra ilustração do dificuldades de aninhamento :-) FWIW, você pode encontrar o código e as tags / code (a outra maneira de fazer código não-bloco) podem conter mais facilmente backticks.
26412
@Pax - entendi. Duh! Eu estava realmente preso mentalmente a códigos de bloqueio por algum motivo.
DVK 26/02
7

$() permite o aninhamento.

out=$(echo today is $(date))

Eu acho que backticks não permite isso.

Shiplu Mokaddim
fonte
2
Você pode aninhar backticks; é muito mais difícil: out=`echo today is \`date\`` .
Jonathan Leffler
4

É o padrão POSIX que define a $(command)forma de substituição de comando. A maioria dos shells em uso atualmente são compatíveis com POSIX e suportam essa forma preferida em relação à notação arcaica de backtick. A seção de substituição de comando (2.6.3) do documento Shell Language descreve isso:

A substituição de comando permite que a saída de um comando seja substituída no lugar do próprio nome do comando. A substituição do comando deve ocorrer quando o comando estiver entre os seguintes:

$(command)

ou (versão com aspas):

`command`

O shell deve expandir a substituição do comando executando o comando em um ambiente de subcamação (consulte Ambiente de Execução do Shell ) e substituindo a substituição do comando (o texto do comando mais o "$ ()" ou aspas anexas) pela saída padrão do comando, removendo sequências de um ou mais <newline>caracteres no final da substituição. embutido<newline>Caracteres antes do final da saída não devem ser removidos; no entanto, eles podem ser tratados como delimitadores de campo e eliminados durante a divisão do campo, dependendo do valor do IFS e da citação que estiver em vigor. Se a saída contiver bytes nulos, o comportamento não será especificado.

No estilo de substituição de comando, com aspas retroativas, <backslash>manterá seu significado literal, exceto quando seguido por: ' $', ' `' ou <backslash>. A pesquisa da cotação correspondente deve ser satisfeita pela primeira cotação não escapada sem aspas; durante esta pesquisa, se uma cotação não escapada for encontrada em um comentário do shell, um documento aqui, uma substituição de comando incorporada do formulário $ ( command ) ou uma sequência de caracteres citada, ocorrerão resultados indefinidos. Uma sequência de aspas simples ou aspas duplas que começa, mas não termina, na `...`sequência " " produz resultados indefinidos.

Com o formulário $ ( comando ), todos os caracteres que seguem o parêntese aberto ao parêntese de fechamento correspondente constituem o comando . Qualquer script de shell válido pode ser usado para comando , exceto um script que consiste apenas em redirecionamentos que produzem resultados não especificados.

Os resultados da substituição de comando não devem ser processados ​​para expansão adicional de til, expansão de parâmetro, substituição de comando ou expansão aritmética. Se uma substituição de comando ocorrer entre aspas duplas, a divisão de campo e a expansão do nome do caminho não devem ser executadas nos resultados da substituição.

A substituição de comando pode ser aninhada. Para especificar o aninhamento na versão com aspas posteriores, o aplicativo deve preceder as aspas internas internas com <backslash>caracteres; por exemplo:

\`command\`

A sintaxe da linguagem de comando do shell tem uma ambiguidade para expansões que começam com "$((", que pode introduzir uma expansão aritmética ou uma substituição de comando que começa com um subshell. A expansão aritmética tem precedência; ou seja, o shell deve primeiro determinar se pode analisar a expansão como uma expansão aritmética e deve analisar apenas a expansão como um comando substituição se determinar que não pode analisar a expansão como uma expansão aritmética. O shell não precisa avaliar expansões aninhadas ao executar essa determinação. Se encontrar o final da entrada sem já ter determinado que não pode analisar a expansão como uma expansão aritmética, o shell O shell deve tratar a expansão como uma expansão aritmética incompleta e reportar um erro de sintaxe. Um aplicativo em conformidade deve garantir que ele separe "$( " e '('em dois tokens (ou seja, separe-os com espaço em branco) em uma substituição de comando que começa com um subshell. Por exemplo, uma substituição de comando contendo um único subshell pode ser escrita como:

$( (command) )

JRFerguson
fonte
4

Esta é uma pergunta legada, mas eu vim com um exemplo perfeitamente válido de $(...)mais `...`.

Eu estava usando uma área de trabalho remota no Windows executando o cygwin e queria iterar sobre o resultado de um comando. Infelizmente, era impossível inserir o caractere backtick, devido à área de trabalho remota ou ao próprio cygwin.

É sensato supor que um cifrão e parênteses serão mais fáceis de digitar em configurações tão estranhas.

Piotr Zierhoffer
fonte