Diferença entre o comando `command` vs o comando `builtin` no Shell Scripting

Diferença entre o comando `command` vs o comando `builtin` no Shell Scripting

Entendo que o comando commandestá especificado nomais recente padrão POSIXe builtinnão é. Também percebo que ambos os comandos sãointegrados regulares(ou seja, eles podem ser substituídos por funções definidas pelo usuário). Alguns shells definem builtin, mas não todos (por exemplo, dashnão). Quero entender por que builtinfoi introduzido em alguns shells.

Pelo que entendi, builtinretornará apenas componentes internos especiais e regulares, mas commandretornará componentes internos especiais, depois internos regulares e, em seguida, comandos no caminho (e a -popção pode ser usada commandpara especificá-lo para uso o padrão definido pelo shell $PATHcaso o usuário tenha modificado o $PATH).

Por exemplo, em mksh, vejo o seguinte:

(OBSERVAÇÃO: mkshinstalado no Ubuntu 20.04 do repo http://archive.ubuntu.com/ubuntu focal/universe amd64 mksh amd64 58-1)

$ echo $KSH_VERSION
@(#)MIRBSD KSH R58 2020/03/27
$ which -a echo
/usr/bin/echo
/bin/echo
$ which -a printf
/usr/bin/printf
/bin/printf
$ type echo
echo is a shell builtin
$ type printf
printf is /usr/bin/printf
$ command echo 'Hello World!'
Hello World!
$ command printf 'Hello World!\n'
Hello World!
$ builtin echo 'Hello World!'
Hello World!
$ builtin printf 'Hello World!\n'
mksh: builtin: printf: not found
$ sudo cp /usr/bin/printf /usr/bin/printf.backup
$ sudo cp /bin/printf /bin/printf.backup
$ sudo rm /usr/bin/printf
$ sudo rm /bin/printf
rm: cannot remove '/bin/printf': No such file or directory
$ sudo cp /usr/bin/printf.backup ~/printf
$ echo $PATH | sed 's/:/\n/g'
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
/usr/games
/usr/local/games
# ...remainder ommitted here for brevity
$ export PATH=~:$PATH
$ echo $PATH | sed 's/:/\n/g'
/home/my_username
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
/usr/games
/usr/local/games
# ...remainder ommitted here for brevity
$ command printf 'Hello World!\n'
Hello World!
$ command -p printf 'Hello World!\n'
mksh: printf: inaccessible or not found

Meu entendimento está correto? Ou fazer commande builtinfazer exatamente a mesma coisa (e se sim, por que foi builtinintroduzido?)? Ou existe outra diferença sutil entre commande builtin?

(Tentei procurar uma resposta no StackExchange, mas não encontrei nada, então se alguém puder me indicar uma resposta adequada, ficaria muito grato.)

ATUALIZAR:

Também é importante notar isso commande builtin"pular" a pesquisa e o uso de aliases definidos. A expansão do alias vem antes da pesquisa e avaliação do comando, assim como a expansão aritmética, variável e curinga de arquivo, na ordem de avaliação do shell POSIX. No entanto, caracteres aritméticos, variáveis ​​e curingas são avaliados em commande builtin, mas não em aliases. Parece algo que os documentos deveriam mencionar.

Por exemplo:

$ bash --version
GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ command echo $((1 + 1))
2
$ builtin echo $((3 + 1))
4

mas

$ bash --version
GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ alias \[='echo hello'
$ [
hello
$ if builtin [ 'north' != 'south' ]; then echo 'what a world!'; fi
what a world!

ATUALIZAÇÃO 2:

Acho que também vale a pena notar que, depois de algumas pesquisas e experiências, descobri que zshse comportaO OPOSTO COMPLETOdo padrão POSIX e outros shells de estilo Bourne em relação ao commandcomando.

Dedocumentos zsh(Seção 6.2, Modificadores de pré-comando)

comando [-pvV]
A palavra de comando é considerada o nome de um comando externo, em vez de uma função shell ou interna.

Por exemplo

$ echo ${ZSH_VERSION}
5.8
$ command cd ~
zsh: command not found: cd
$ command -p cd ~
zsh: command not found: cd
$ command echo 'hi'
hi
# `echo` is a regular builtin just like `cd` though...??!!!
$ set -o |grep posix
posixaliases          off
posixargzero          off
posixbuiltins         off
posixcd               off
posixidentifiers      off
posixjobs             off
posixstrings          off
posixtraps            off
$ cd() { echo 'I told you.'; }
$ cd
I told you.
# ????!!!!

Somente se a POSIX_BUILTINSvariável de ambiente estiver definida (use set -o posixbuiltins) o comando commandtambém executará componentes internos especiais e regulares.

Por exemplo

$ echo ${ZSH_VERSION}
5.8
$ cd /
$ ls
bin   dev  home  lib    lib64   lost+found  mnt  proc  run   snap  sys  usr
boot  etc  init  lib32  libx32  media       opt  root  sbin  srv   tmp  var
$ set -o posixbuiltins
$ command cd ~
$ ls
Desktop    Downloads  Pictures  Templates
Documents  Music      Public    Videos
$ command -p cd /
$ ls
bin   dev  home  lib    lib64   lost+found  mnt  proc  run   snap  sys  usr
boot  etc  init  lib32  libx32  media       opt  root  sbin  srv   tmp  var

Por outro lado, dodocumentos bash

comando
comando [-pVv]comando [argumentos…]

Executa o comando com argumentos ignorando qualquer função shell chamada comando. Somente comandos internos do shell ou comandos encontrados pesquisando o PATH são executados.

Por exemplo

$ bash --version
GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ cd /
$ ls
bin   dev  home  lib    lib64   lost+found  mnt  proc  run   snap  sys  usr
boot  etc  init  lib32  libx32  media       opt  root  sbin  srv   tmp  var
$ set +o posix  # Turn off POSIX mode
$ command cd ~
$ ls
Desktop    Downloads  Pictures  Templates
Documents  Music      Public    Videos
$ command -p cd /
$ ls
bin   dev  home  lib    lib64   lost+found  mnt  proc  run   snap  sys  usr
boot  etc  init  lib32  libx32  media       opt  root  sbin  srv   tmp  var

Então zshse comportaO OPOSTO COMPLETOde outros shells do estilo Bourne em relação ao commandcomando... estou começando a não gostar zshcada vez mais... comprador, cuidado (caveat emptor) Eu suponho.

ATUALIZAÇÃO 3:

Também é importante notar que ksh88não possui um commandcomando integrado. Isto foi introduzido em ksh93. Para substituir um ksh88, você teria que usar uma combinação estranha de alias, funções e aspas.

(Fonte: Robbins, Arnold; Rosenblatt, Bill. Aprendendo o Korn Shell: Programação Unix (p. 456). O'Reilly Media. Edição Kindle.)

Isso é consistente com a resposta de @Gilles 'SO- pare de ser mau'.

Responder1

OJustificativa POSIX paracommandresponde à maioria dos aspectos históricos da sua pergunta.

Ocomandoutilitário é um pouco semelhante ao shell da Oitava Ediçãoconstruídas emcomando, mas desdecomandotambém vai ao sistema de arquivos para procurar utilitários, o nomeconstruídas emnão seria intuitivo.

(…) Ocomando -ve-Vopções foram adicionadas para satisfazer os requisitos dos usuários que atualmente são atendidos por três utilitários históricos diferentes:tipono shell do System V,de ondeno KornShell equalno shell C.

NoOitava Edição sh, o builtinbuiltin foi documentado apenas como ignorando funções:

Execute o comando especial integrado (como break) independentemente das funções definidas com o mesmo nome.

Os aliases ainda não existiam (e quando apareceram, havia diferentes mecanismos para contorná-los). Se você quisesse ignorar uma função para executar um comando externo, você poderia fornecer seu caminho completo, que tinha a vantagem de especificar exatamente o que você queria executar caso houvesse vários executáveis ​​com esse nome no caminho de busca do comando. A portabilidade entre sistemas onde o caminho completo para um comando pode ser diferente não era uma preocupação generalizada. O mesmo builtinaconteceu com a única coisa que realmente não poderia ser feita de outra maneira.

Mais tarde, o POSIX veio e adicionou uma forma padrão de ignorar uma função. Neste contexto, a portabilidade para sistemas onde os comandos externos estavam em locais diferentes era uma grande preocupação, por isso builtinnão era suficiente, daí o novo commandque ignora funções (e aliases, uma vez que command foocoloca foonuma posição onde os aliases não são expandidos) e encontra padrão comandos. (Também hoje o ksh tem um chamado interno builtinque faz algo completamente diferente, mas não sei se veio antes ou depois da criação do POSIX command.) No entanto, commandpropositalmente não pula comandos internos, novamente devido a questões de portabilidade: se um sh programas invocam um comando padrão, cabe ao sistema operacional escolher se esse comando pode ser fornecido como integrado. commandcancela o comportamento “integrado especial” de componentes internos especiais, novamente para que o aplicativo não precise saber se está invocando um componente interno ou não.

Não sei por que o zsh commandignora os componentes internos quando não está no modo POSIX (especificamente, quando oposix_builtinsopçãonão está definido). Sua implementação atual commandremonta a uma mudança em maio de 1996 lançada emzsh 2.6 beta 20(“remover -, exec, noglob e comando da lista de palavras reservadas”). Como essa implementação já tinha um tratamento diferente para o modo POSIX, presumo que fosse para compatibilidade retroativa com uma versão anterior do zsh, mas não investiguei mais. Pode ser deliberado porque, se posix_builtinsnão estiver definido, os integrados não serão necessariamente compatíveis com POSIX e, portanto, é melhor não invocá-los se um aplicativo usar o commandcomando especificamente POSIX.

informação relacionada