Por que não usar "qual"? O que usar então?


328

Ao olhar para o caminho para um executável ou verificar o que aconteceria se você inserir um nome de comando em um shell Unix, há uma infinidade de utilidades diferentes ( which, type, command, whence, where, whereis, whatis, hash, etc).

Muitas vezes ouvimos que isso whichdeve ser evitado. Por quê? O que devemos usar em vez disso?


3
Eu acho que a maioria dos argumentos contra o uso whichestá assumindo um contexto de shell interativo. Esta pergunta está marcada / portabilidade. Então, eu interpreto a pergunta neste contexto como "o que usar em vez de whichencontrar o primeiro executável de um determinado nome no $PATH". A maioria das respostas e razões contra whichlidar com aliases, buildins e funções, que na maioria dos scripts de shell portáteis do mundo real são apenas de interesse acadêmico. Aliases definidos localmente não são herdados ao executar um script de shell (a menos que você o forneça .).
21814 MattBianco

5
@MattBianco, sim, csh(e whichainda é um cshscript na maioria dos Unices comerciais) é lido ~/.cshrcquando não interativo. É por isso que você notará que os scripts csh geralmente começam #! /bin/csh -f. whichnão porque pretende fornecer os aliases, porque é uma ferramenta para usuários (interativos) de csh. Usuários de shells POSIX possuem command -v.
Stéphane Chazelas

@rudimeier, a resposta seria sempre, a menos que seu shell seja (t)csh(ou você não se importe se ele não fornecer o resultado correto), use typeou em command -vvez disso . Veja as respostas para o porquê .
Stéphane Chazelas

1
@rudimeier, ( stat $(which ls)está errado por vários motivos (falta --, falta de aspas), não apenas o uso de which). Você usaria stat -- "$(command -v ls)". Isso pressupõe, de lsfato, um comando encontrado no sistema de arquivos (não um componente do seu shell ou função de alias). whichpode dar-lhe o caminho errado (não o caminho que o seu shell seria executado se você entrou ls) ou dar-lhe um alias, conforme definido na configuração de alguns outros conchas ...
Stéphane Chazelas

1
@rudimeier, novamente, existem várias condições sob as quais muitas whichimplementações não forneceriam a você o lsque seria encontrado por uma pesquisa $PATH(independentemente do que lspossa ser chamado no seu shell). sh -c 'command -v ls', ou zsh -c 'rpm -q --whatprovides =ls'são mais propensos a fornecer a resposta correta. O ponto aqui é que whiché uma herança quebrada de csh.
Stéphane Chazelas

Respostas:


366

Aqui está tudo o que você nunca pensou que nunca iria querer saber sobre isso:

Sumário

Para obter o nome do caminho de um executável em um script de shell semelhante ao Bourne (existem algumas ressalvas; veja abaixo):

ls=$(command -v ls)

Para descobrir se um determinado comando existe:

if command -v given-command > /dev/null 2>&1; then
  echo given-command is available
else
  echo given-command is not available
fi

No prompt de um shell interativo tipo Bourne:

type ls

O whichcomando é uma herança quebrada do C-Shell e é melhor ficar sozinho em conchas semelhantes a Bourne.

Casos de Uso

Há uma distinção entre procurar essas informações como parte de um script ou interativamente no prompt do shell.

No prompt do shell, o caso de uso típico é: esse comando se comporta de maneira estranha, estou usando o correto? O que exatamente aconteceu quando eu digitei mycmd? Posso olhar mais para o que é?

Nesse caso, você deseja saber o que seu shell faz quando invoca o comando sem realmente invocá-lo.

Nos scripts shell, ele tende a ser bem diferente. Em um script de shell, não há razão para você querer saber onde ou qual é o comando, se tudo o que você quer fazer é executá-lo. Geralmente, o que você deseja saber é o caminho do executável, para que você possa obter mais informações (como o caminho para outro arquivo relativo a ele ou ler informações do conteúdo do arquivo executável nesse caminho).

Interativamente, você pode querer saber sobre todos os my-cmdcomandos disponíveis no sistema, em scripts, raramente.

A maioria das ferramentas disponíveis (como costuma ser o caso) foram projetadas para serem usadas interativamente.

História

Um pouco da história primeiro.

O início do shell do Unix até o final dos anos 70 não tinha funções ou pseudônimos. Somente a procura tradicional de executáveis ​​em $PATH. cshintroduziu aliases por volta de 1978 (embora tenha cshsido lançado pela primeira vez em 2BSDmaio de 1979), e também o processamento de um .cshrcpara os usuários personalizarem o shell (todo shell, como se csh.cshrcmesmo quando não é interativo, como nos scripts).

enquanto o shell Bourne foi lançado pela primeira vez no Unix V7 no início de 1979, o suporte a funções só foi adicionado muito mais tarde (1984 no SVR2) e, de qualquer forma, ele nunca teve algum rcarquivo ( .profileé para configurar seu ambiente, não o shell em si ).

csh ficou muito mais popular que o shell Bourne porque (embora tivesse uma sintaxe muito pior que o shell Bourne), estava adicionando muitos recursos mais convenientes e agradáveis ​​para uso interativo.

Em 3BSD(1980), um whichscript csh foi adicionado para que os cshusuários ajudassem a identificar um executável, e é um script dificilmente diferente que você pode encontrar hoje whichem muitos Unices comerciais (como Solaris, HP / UX, AIX ou Tru64).

Esse script lê o usuário ~/.cshrc(como todos os cshscripts fazem, a menos que seja invocado com csh -f) e consulta os nomes de comando fornecidos na lista de aliases e em $path(a matriz cshmantida com base em $PATH).

Aqui está, whichprimeiro lugar para o shell mais popular da época (e cshainda era popular até meados dos anos 90), que é a principal razão pela qual foi documentado em livros e ainda é amplamente utilizado.

Observe que, mesmo para um cshusuário, esse whichscript csh não fornece necessariamente as informações corretas. Ele obtém os aliases definidos em ~/.cshrc, não os que você pode ter definido mais tarde no prompt ou, por exemplo, sourceinserindo outro csharquivo e (embora isso não seja uma boa idéia), PATHpossa ser redefinido ~/.cshrc.

A execução desse whichcomando a partir de um shell Bourne ainda procuraria aliases definidos em seu diretório ~/.cshrc, mas se não houver um porque você não o usa csh, provavelmente ainda obterá a resposta certa.

Uma funcionalidade semelhante não foi adicionada ao shell Bourne até 1984 no SVR2 com o typecomando embutido. O fato de ele estar embutido (em oposição a um script externo) significa que ele pode fornecer as informações corretas (até certo ponto), pois ele tem acesso aos elementos internos do shell.

O typecomando inicial sofreu um problema semelhante ao do whichscript, pois não retornou um status de saída com falha se o comando não foi encontrado. Além disso, para os executáveis, ao contrário which, ele gera algo como, em ls is /bin/lsvez de apenas o /bin/lsque tornou menos fácil o uso em scripts.

O shell Bourne da versão 8 do Unix (não lançado na natureza) tinha o typenome incorporado whatis. E o shell do Plan9 (o futuro sucessor do Unix) rc(e seus derivados como akangae es) também tem whatis.

O shell Korn (um subconjunto no qual a definição sh POSIX se baseia), desenvolvido em meados dos anos 80, mas não amplamente disponível antes de 1988, adicionou muitos dos cshrecursos (editor de linha, aliases ...) no topo do shell Bourne . Ele adicionou seu próprio whencebuiltin (além de type), que tomou várias opções ( -vpara fornecer a typesaída detalhada semelhante a, e -pprocurar apenas executáveis ​​(não aliases / funções ...)).

Coincidentemente à turbulência com relação às questões de direitos autorais entre a AT&T e Berkeley, algumas implementações de shell de software livre surgiram no final dos anos 80 e início dos 90. Todo o shell Almquist (ash, para substituir o shell Bourne nos BSDs), a implementação de domínio público do ksh (pdksh) bash(patrocinada pela FSF), zshfoi lançada entre 1989 e 1991.

Ash, apesar de ser um substituto para o shell Bourne, não tinha um typebuilt-in até muito mais tarde (no NetBSD 1.3 e no FreeBSD 2.3), embora tivesse hash -v. O OSF / 1 /bin/shtinha um typebuilt-in que sempre retornava 0 até OSF / 1 v3.x. bashnão whenceadicionou uma -popção, mas adicionou uma opção para typeimprimir o caminho ( type -pseria como whence -p) e -arelatar todos os comandos correspondentes. tcshfez whichbuiltin e adicionou um wherecomando agindo como bash's type -a. zshtem todos eles.

O fishshell (2005) possui um typecomando implementado como uma função.

O whichroteiro csh entretanto foi removido do NetBSD (como era embutido no tcsh e de não muito uso em outras conchas), e a funcionalidade adicional para whereis(quando invocado como which, whereisse comporta como whichexceto que ele só olha para cima executáveis em $PATH). No OpenBSD e no FreeBSD, whichtambém foi alterado para um escrito em C que consulta $PATHapenas os comandos .

Implementações

Existem dezenas de implementações de um whichcomando em vários Unices com sintaxe e comportamento diferentes.

No Linux (além dos integrados no tcshe zsh), encontramos várias implementações. Nos sistemas Debian recentes, por exemplo, é um simples shell script POSIX que procura por comandos $PATH.

busyboxtambém tem um whichcomando

Há uma GNU whichque é provavelmente a mais extravagante. Ele tenta estender o que o whichscript csh fez a outros shells: você pode dizer quais são seus apelidos e funções para que ele possa lhe dar uma resposta melhor (e acredito que algumas distribuições Linux definem alguns apelidos globais em torno disso para bashfazer isso) .

zshpossui alguns operadores para expandir para o caminho dos executáveis: o operador de = expansão de nome de arquivo e o :cmodificador de expansão de histórico (aqui aplicado à expansão de parâmetro ):

$ print -r -- =ls
/bin/ls
$ cmd=ls; print -r -- $cmd:c
/bin/ls

zsh, no zsh/parametersmódulo também cria a tabela de hash do comando como a commandsmatriz associativa:

$ print -r -- $commands[ls]
/bin/ls

O whatisutilitário (exceto aquele no shell Unix V8 Bourne ou no Plan 9 rc/ es) não é realmente relacionado, pois é apenas para documentação (mostra o banco de dados whatis, que é a sinopse da página de manual).

whereistambém foi adicionado 3BSDao mesmo tempo em whichque foi gravado e Cnão cshé usado para procurar ao mesmo tempo o executável, a página de manual e a fonte, mas não com base no ambiente atual. Então, novamente, isso responde a uma necessidade diferente.

Agora, na frente padrão, o POSIX especifica os comandos command -ve -V(que eram opcionais até o POSIX.2008). UNIX especifica o typecomando (sem opção). Isso é tudo ( where, which, whencenão são especificados em qualquer padrão)

Até algumas versões, typee command -veram opcionais na especificação Linux Standard Base, o que explica por que, por exemplo, algumas versões antigas posh(embora baseadas nas pdkshque tinham as duas) também não tinham. command -vtambém foi adicionado a algumas implementações de shell Bourne (como no Solaris).

Status Hoje

Atualmente, o status é esse typee command -vé onipresente em todas as conchas do tipo Bourne (embora, como observado por @jarno, observe a advertência / bug bashquando não estiver no modo POSIX ou alguns descendentes do shell Almquist abaixo nos comentários). tcshé o único shell em que você gostaria de usar which(como não typeexiste e whichestá embutido).

Nos outros do que conchas tcshe zsh, whichpode dizer-lhe o caminho do executável dado enquanto não há nenhuma alias ou função por esse mesmo nome em qualquer um dos nossos ~/.cshrc, ~/.bashrcou qualquer arquivo de inicialização shell e você não definir $PATHa sua ~/.cshrc. Se você tiver um alias ou função definido para ele, ele pode ou não falar sobre isso ou dizer a coisa errada.

Se você quiser saber sobre todos os comandos com um determinado nome, não há nada portátil. Você usaria whereem tcshou zsh, type -ana bashou zsh, whence -ano ksh93 e em outros shells, você pode usar typeem combinação com which -ao que pode trabalhar.

Recomendações

Obtendo o nome do caminho para um executável

Agora, para obter o nome do caminho de um executável em um script, existem algumas ressalvas:

ls=$(command -v ls)

seria a maneira padrão de fazê-lo.

Existem alguns problemas, porém:

  • Não é possível conhecer o caminho do executável sem executá-lo. Todos os type, which, command -v... todas as heurísticas de uso para descobrir o caminho. Eles percorrem os $PATHcomponentes e localizam o primeiro arquivo que não é de diretório para o qual você tem permissão de execução. No entanto, dependendo do shell, quando se trata de executar o comando, muitos deles (Bourne, AT&T ksh, zsh, ash ...) apenas os executam na ordem de $PATHaté que a execvechamada do sistema não retorne com erro . Por exemplo, se $PATHcontém /foo:/bare você deseja executar ls, eles primeiro tentarão executar /foo/lsou se isso falhar /bar/ls. Agora execução de/foo/lspode falhar porque você não tem permissão de execução, mas também por muitos outros motivos, como se não fosse um executável válido. command -v lsinformaria /foo/lsse você tiver permissão de execução /foo/ls, mas a execução lspode realmente ser executada /bar/lsse /foo/lsnão for um executável válido.
  • if fooé um builtin ou função ou alias, command -v fooretorna foo. Com alguns shells como ash, pdkshou zsh, ele também pode retornar foose $PATHincluir a string vazia e houver um fooarquivo executável no diretório atual. Existem algumas circunstâncias em que você pode levar isso em consideração. Lembre-se, por exemplo, de que a lista de componentes internos varia com a implementação do shell (por exemplo, mountàs vezes é incorporada ao busybox sh) e, por exemplo, bashpode obter funções do ambiente.
  • se $PATHcontiver componentes de caminho relativo (normalmente .ou a cadeia vazia que se refere ao diretório atual, mas pode ser qualquer coisa), dependendo do shell, command -v cmdpode não gerar um caminho absoluto. Portanto, o caminho que você obtém no momento da execução command -vnão será mais válido depois de você cdem outro lugar.
  • Anedótica: com o shell ksh93, se /opt/ast/bin(embora esse caminho exato pode variar em diferentes sistemas creio eu) está em você $PATH, ksh93 vai disponibilizar algumas builtins extras ( chmod, cmp, cat...), mas command -v chmodvai voltar /opt/ast/bin/chmodmesmo se esse caminho doesn' não existe.

Determinando se um Comando Existe

Para descobrir se um determinado comando existe de maneira padrão, você pode:

if command -v given-command > /dev/null 2>&1; then
  echo given-command is available
else
  echo given-command is not available
fi

Onde alguém pode querer usar which

(t)csh

Em cshe tcshvocê não tem muita escolha. Em tcsh, tudo bem como whichestá embutido. Em csh, esse será o whichcomando do sistema , que pode não fazer o que você deseja em alguns casos.

encontre comandos apenas em algumas conchas

Um caso em que pode fazer sentido usar whiché se você quer saber o caminho de um comando, ignorando builtins shell potenciais ou funções em bash, csh(não tcsh), dashou Bourneshell scripts que é conchas que não têm whence -p(como kshou zsh) , command -ev(like yash), whatis -p( rc, akanga) ou um builtin which(like tcshou zsh) em sistemas onde whichestá disponível e não é o cshscript.

Se essas condições forem atendidas, então:

echo=$(which echo)

forneceria o caminho do primeiro echoem $PATH(exceto nos casos de canto), independentemente de ser echotambém um shell builtin / alias / function ou não.

Em outras conchas, você prefere:

  • zsh : echo==echoou echo=$commands[echo]ouecho=${${:-echo}:c}
  • ksh , zsh :echo=$(whence -p echo)
  • yash :echo=$(command -ev echo)
  • rc , akanga : echo=`whatis -p echo`(cuidado com os caminhos com espaços)
  • peixe :set echo (type -fp echo)

Observe que, se tudo o que você quer fazer é executar esse echocomando, você não precisa obter o caminho, basta:

env echo this is not echoed by the builtin echo

Por exemplo, com tcsh, para impedir que o builtin whichseja usado:

set Echo = "`env which echo`"

quando você precisa de um comando externo

Outro caso em que você pode querer usar whiché quando você realmente precisa de um comando externo. O POSIX exige que todos os recursos internos do shell (como command) também estejam disponíveis como comandos externos, mas infelizmente esse não é o caso commandem muitos sistemas. Por exemplo, é raro encontrar um commandcomando em sistemas operacionais baseados em Linux, enquanto a maioria deles possui um whichcomando (embora diferentes, com opções e comportamentos diferentes).

Os casos em que você pode querer um comando externo estão onde quer que você execute um comando sem chamar um shell POSIX.

As funções system("some command line"), popen()... do C ou de vários idiomas chamam um shell para analisar essa linha de comando, assim system("command -v my-cmd")como o trabalho neles. Uma exceção a isso seria perlotimizar o shell se ele não vir nenhum caractere especial do shell (além do espaço). Isso também se aplica ao seu operador de backtick:

$ perl -le 'print system "command -v emacs"'
-1
$ perl -le 'print system ":;command -v emacs"'
/usr/bin/emacs
0

$ perl -e 'print `command -v emacs`'
$ perl -e 'print `:;command -v emacs`'
/usr/bin/emacs

A adição disso :;acima força perla invocar uma concha lá. Ao usar which, você não precisaria usar esse truque.


24
@ Joe, whiché um cshscript em muitos escritórios comerciais. O motivo é histórico, foi por isso que dei a história, para que as pessoas entendam de onde ela veio, por que as pessoas se acostumaram a usá-lo e por que, na verdade, não há motivo para você usá-lo. E sim, algumas pessoas usam (t) csh. Nem todo mundo usa Linux ainda
Stéphane Chazelas

12
Depois de ler este post, encontrei muito contexto para a resposta, mas não a resposta em si. Onde, neste post, ele realmente diz por que não usar which, em oposição às coisas que você está tentando usar which, o histórico de which, implementações de whichoutros comandos para executar tarefas relacionadas ou razões para usá-lo which? Por que os outros comandos são melhores ? Do que eles fazem diferente which? Como eles evitam suas armadilhas? Esta resposta realmente gasta mais palavras nos problemas com as alternativas do que com os problemas which.
user62251

1
Ao contrário do que a resposta afirma, command -vnão verifica a permissão de execução, pelo menos se você a chamar pelo argumento de nome de arquivo puro sem caminho. Eu testei pelo traço 0.5.8 e GNU bash 4.3.48.
Jarno

2
@ StéphaneChazelas Se eu criar um novo arquivo touch /usr/bin/mytestfilee depois executá- command -v mytestfilelo, ele fornecerá o caminho (enquanto which mytestfileque não).
Jarno

2
@jarno, oh sim, você está certo. bashse instalará em um arquivo não executável se não puder encontrar um executável, por isso está "OK" (embora na prática prefira command -v/ typeretorne um erro), pois esse é o comando que ele tentaria executar quando você executar mytestfile, mas o dasho comportamento é incorreto, como se houvesse um não executável cmdà frente de um executável, command -vretornasse o não executável enquanto a execução cmdexecutaria o executável (o errado também é hash). O FreeBSD sh(também baseado em ash) possui o mesmo bug. zsh, yash, ksh, mksh, bash como sh estão OK.
Stéphane Chazelas

47

As razões pelas quais alguém pode não querer usar whichjá foram explicadas, mas aqui estão alguns exemplos em alguns sistemas em que whichrealmente falha.

Em shells semelhantes a Bourne, estamos comparando a saída whichcom a saída de type( typesendo um shell incorporado, deve ser a verdade básica, pois é o shell nos dizendo como chamaria um comando).

Muitos casos são de canto , mas lembre-se de que which/ typesão freqüentemente usados ​​em casos de canto (para encontrar a resposta para um comportamento inesperado como: por que diabos esse comando está se comportando assim, para quem eu estou chamando? ).

A maioria dos sistemas, a maioria das cascas tipo Bourne: funções

O caso mais óbvio é para funções:

$ type ls
ls is a function
ls ()
{
[ -t 1 ] && set -- -F "$@";
command ls "$@"
}
$ which ls
/bin/ls

O motivo é que whichapenas relatórios sobre executáveis ​​e, às vezes, sobre aliases (embora nem sempre sejam os do seu shell), não funcionam.

O GNU cuja página de manual possui um exemplo quebrado (como eles se esqueceram de citar $@) de como usá-lo para reportar funções também, mas assim como para aliases, porque não implementa um analisador de sintaxe de shell, é facilmente enganado:

$ which() { (alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot "$@";}
$ f() { echo $'\n}\ng ()\n{ echo bar;\n}\n' >> ~/foo; }
$ type f
f is a function
f ()
{
echo '
}
g ()
{ echo bar;
}
' >> ~/foo
}
$ type g
bash: type: g: not found
$ which f
f ()
{
echo '
}
$ which g
g ()
{ echo bar;
}

A maioria dos sistemas, a maioria dos invólucros Bourne: builtins

Outro caso óbvio é builtins ou palavras-chave, como whichsendo um comando externo não tem nenhuma maneira de saber qual builtins sua concha tem (e alguns escudos como zsh, bashou kshpode carregar builtins dinamicamente):

$ type echo . time
echo is a shell builtin
. is a shell builtin
time is a shell keyword
$ which echo . time
/bin/echo
which: no . in (/bin:/usr/bin)
/usr/bin/time

(que não se aplica a zshonde whichestá embutido)

Solaris 10, AIX 7.1, HP / UX 11i, Tru64 5.1 e muitos outros:

$ csh
% which ls
ls:   aliased to ls -F
% unalias ls
% which ls
ls:   aliased to ls -F
% ksh
$ which ls
ls:   aliased to ls -F
$ type ls
ls is a tracked alias for /usr/bin/ls

Isso ocorre porque na maioria dos Unices comerciais which(como na implementação original no 3BSD) é um cshscript que lê ~/.cshrc. Os aliases que serão relatados são os definidos lá, independentemente dos aliases que você definiu atualmente e independentemente do shell que você está realmente usando.

No HP / UX ou Tru64:

% echo 'setenv PATH /bin:/usr/bin' >> ~/.cshrc
% setenv PATH ~/bin:/bin:/usr/bin
% ln -s /bin/ls ~/bin/
% which ls
/bin/ls

(as versões Solaris e AIX corrigiram esse problema salvando $pathantes de ler ~/.cshrce restaurando antes de procurar o (s) comando (s))

$ type 'a b'
a b is /home/stephane/bin/a b
$ which 'a b'
no a in /usr/sbin /usr/bin
no b in /usr/sbin /usr/bin

Ou:

$ d="$HOME/my bin"
$ mkdir "$d"; PATH=$PATH:$d
$ ln -s /bin/ls "$d/myls"
$ type myls
myls is /home/stephane/my bin/myls
$ which myls
no myls in /usr/sbin /usr/bin /home/stephane/my bin

(é claro, sendo um cshscript, você não pode esperar que funcione com argumentos que contenham espaços ...)

CentOS 6.4, bash

$ type which
which is aliased to `alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
$ alias foo=': "|test|"'
$ which foo
alias foo=': "|test|"'
        /usr/bin/test
$ alias $'foo=\nalias bar='
$ unalias bar
-bash: unalias: bar: not found
$ which bar
alias bar='

Nesse sistema, existe um apelido definido em todo o sistema que agrupa o whichcomando GNU .

A saída falsa é porque whichlê a saída do bash's alias, mas não sabe como analisar corretamente e usa heurísticas (um apelido por linha, procura o primeiro encontrado comando depois de um |, ;, &...)

A pior coisa do CentOS é que ele zshpossui um whichcomando interno perfeitamente bem, mas o CentOS conseguiu quebrá-lo, substituindo-o por um alias que não funcionava no GNU which.

Debian 7.0, ksh93:

(embora se aplique à maioria dos sistemas com muitos shells)

$ unset PATH
$ which which
/usr/local/bin/which
$ type which
which is a tracked alias for /bin/which

No Debian, /bin/whiché um /bin/shscript. No meu caso, shser , dashmas é o mesmo quando é bash.

Um unset PATHnão é para desativar PATHa pesquisa, mas significa utilizar do sistema PATH padrão que, infelizmente, no Debian, ninguém concorda com ( dashe bashtem /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin, zshtem /bin:/usr/bin:/usr/ucb:/usr/local/bin, ksh93tem /bin:/usr/bin, mkshtem /usr/bin:/bin( $(getconf PATH)), execvp()(como em env) tem :/bin:/usr/bin(sim, procura no diretório atual primeiro! )).

É por isso que whichdeixa ele errada acima, uma vez que está usando dash's padrão PATHque é diferente de ksh93' s

Não é melhor com o GNU whichque relata:

which: no which in ((null))

(curiosamente, existe realmente um /usr/local/bin/whichno meu sistema que é realmente um akangascript que acompanha akanga(um rcderivado do shell em que o padrão PATHé /usr/ucb:/usr/bin:/bin:.))

bash, qualquer sistema:

Aquele a quem Chris está se referindo em sua resposta :

$ PATH=$HOME/bin:/bin
$ ls /dev/null
/dev/null
$ cp /bin/ls bin
$ type ls
ls is hashed (/bin/ls)
$ command -v ls
/bin/ls
$ which ls
/home/chazelas/bin/ls

Também depois de ligar hashmanualmente:

$ type -a which
which is /usr/local/bin/which
which is /usr/bin/which
which is /bin/which
$ hash -p /bin/which which
$ which which
/usr/local/bin/which
$ type which
which is hashed (/bin/which)

Agora, um caso em que, whichàs vezes, typefalha:

$ mkdir a b
$ echo '#!/bin/echo' > a/foo
$ echo '#!/' > b/foo
$ chmod +x a/foo b/foo
$ PATH=b:a:$PATH
$ which foo
b/foo
$ type foo
foo is b/foo

Agora, com algumas conchas:

$ foo
bash: ./b/foo: /: bad interpreter: Permission denied

Com outros:

$ foo
a/foo

Nem whichnem typepode saber de antemão que b/foonão pode ser executado. Algumas conchas como bash, kshou yash, ao invocar foovai realmente tentar executar b/fooe relatar um erro, enquanto outros (como zsh, ash, csh, Bourne, tcsh) será executado a/fooapós o fracasso da execve()chamada de sistema on b/foo.


mkshna verdade, usa algo diferente para o padrão $PATH: primeiro, a constante em tempo de compilação do sistema operacional _PATH_DEFPATHé usada (geralmente nos BSDs), depois confstr(_CS_PATH, …)é usada (POSIX) e, se ambas não existirem ou falharem, /bin:/usr/bin:/sbin:/usr/sbinserá usada.
mirabilos

1
No seu 1º exemplo, mesmo que lsseja uma função que está sendo usada lsno PATH. E whiché bom dizer qual é o usado /usr/bin/ls ou /usr/local/bin/ls. Eu não vejo "Por que não usar que" ....
rudimeier

@rudimeier, Isso which lsvai me dar, /bin/lsindependentemente de a lsfunção chamar /bin/lsou /opt/gnu/bin/lsou dirou nada. IOW, which(aquela que implementações, IMMV) está dando algo irrelevante
Stéphane Chazelas

1
@ StéphaneChazelas. Não não não. Eu sei já que a minha lsé uma função. Eu sei que minha lsfunção está chamando lsde PATH. Agora whichme diz onde está o arquivo. Você vê apenas um único caso de uso: "O que meu shell faria com este comando". Para este caso de uso whichestá errado, correto. Mas há outros casos de uso em que (GNU) whiché exatamente a coisa certa.
Rudimeier 3/17

@rudimeter, depende da whichimplementação. Alguns dirão que é um apelido (se você tiver um apelido configurado ou se houver um apelido ~/.cshrcem sua casa que possua esse apelido), outros darão um caminho, mas o caminho errado, sob algumas condições. sh -c 'command -v ls', embora não seja perfeito, é mais provável que você dê a resposta certa para esse requisito diferente (e também é padrão).
Stéphane Chazelas

21

Uma coisa que (pela minha rápida olhada) parece que Stephane não mencionou é que whichnão tem idéia da tabela de hash do caminho do seu shell. Isso tem o efeito de retornar um resultado que não é representativo do que realmente é executado, o que o torna ineficaz na depuração.


6

No espírito do UNIX: Faça com que cada programa faça uma coisa bem.

Se o objetivo é responder: Qual executável existe com esse nome?

O programa executável fornecido com os sistemas Debian é uma boa resposta. O que forneceu o csh incluía aliases, que é uma fonte de problemas. O que algumas conchas fornecem como um interno interno tem um objetivo diferente. Use esse executável ou use o script fornecido no final desta resposta.

Se esse script for usado, o que ele responde é limpo, simples e útil.

Este objetivo corresponderá à primeira frase da sua pergunta:

Ao procurar o caminho para um executável……

Se você possui um sistema que não possui um executável chamado, o qual (a maioria dos sistemas Linux possui um), você pode criar um ~/bin/whichanteriormente /bin/no PATH, para que os executáveis ​​pessoais substituam os do sistema como o da parte inferior deste post:

Esse executável listará (por padrão) todos os executáveis ​​encontrados no PATH. Se apenas o primeiro for necessário, a opção -festará disponível.


Nesse ponto, caímos em um objetivo diferente:

o que o shell executará (após a análise)

Isso vem da sua segunda frase:

verificando o que aconteceria se você inserir um nome de comando em um shell Unix

Este segundo assunto tenta encontrar uma boa resposta para uma pergunta que é bastante difícil de responder. Os reservatórios têm visões divergentes, caixas de canto e (no mínimo) interpretações diferentes. Adicionando a isso:

há uma infinidade de utilitários diferentes (que, tipo, comando, de onde, onde, onde está, o que é, hash, etc.).

E claro, todas as tentativas correspondem a esse objetivo.


Evitar qual?

Muitas vezes ouvimos o que deve ser evitado.

Eu me pergunto: Por que isso deveria ser dito se whichfunciona bem (pelo menos no debian)?

No espírito do UNIX: Faça com que cada programa faça uma coisa bem.

O programa externowhich está fazendo uma coisa: Encontre o primeiro executável no PATH que tenha o mesmo nome que o nome do comando . E está fazendo isso razoavelmente bem.

Não conheço nenhum outro programa ou utilitário que responda a essa pergunta de uma maneira mais fundamental. Como tal, é útil e pode ser usado quando necessário.

A alternativa mais próxima parece ser command -pv commandName:, mas isso também informará sobre buildins e aliases. Não é a mesma resposta.

Claro, whiché limitado, não responde a todas as perguntas, nenhuma ferramenta poderia fazer isso (bem, ainda não ...). Mas é útil quando usado para responder à pergunta que foi projetada para responder (a logo acima). Muito parecido edfoi limitado e depois sedapareceu (ou vi/ vim). Ou como awkfoi limitado e fez Perl aparecer e estender. No entanto, ed, sede / ou awktem casos de uso específicos onde vimou perlsão não as melhores ferramentas.

Por quê?

Provavelmente porque whichresponde apenas a uma parte da pergunta que um usuário de shell pode fazer:

O que está sendo executado quando eu digito um commandName?


Externo que

Que deve estar disponível (em muitos sistemas) como um executável externo.
A única maneira de chamar essa ferramenta externa é usar env para sair do shell e depois chamar which(que funciona em todos os shells):

 $ env which which
 /usr/bin/which

Ou use o caminho completo para which(que pode variar em diferentes sistemas):

 /usr/bin/which which 

Por que isso é hacknecessário? Como algumas conchas (especialmente zsh) ocultam which:

 $ zsh -c 'which which'
 which: shell built-in command

Ser uma ferramenta externa (como env) explica perfeitamente por que não reportará informações internas do shell. Como aliases, funções, builtins, builtins especiais, variáveis ​​de shell (não exportadas), etc:

 $ env which ls
 /usr/bin/ls
 $ env which ll       # empty output

A saída vazia de ll(um alias comum para ll='ls -l') indica que llnão está relacionado a um programa executável ou, pelo menos, que não há nenhum arquivo executável nomeado llno PATH. O uso de lldeve chamar outra coisa, neste caso, um alias:

 $ type ll
 ll is aliased to `ls -l'

type e command

Os comandos typee command -vsão solicitados pelo POSIX. Espera-se que eles trabalhem na maioria das conchas, e funcionam, exceto em csh, tcsh, fish e rc.

Ambos os comandos podem ser usados ​​para fornecer outro ponto de vista sobre o qual o comando será executado.

whence, where, whereis, whatis,hash

Então, há whence, where, whereis, whatis, hash, e alguns outros. Todas as respostas diferentes para perguntas semelhantes. Todos funcionam de maneiras diferentes em conchas diferentes. Provavelmente, whenceé o mais comum depois type. Os outros são soluções especiais que respondem à mesma pergunta de maneiras diferentes.

O que devemos usar em vez disso?

Provavelmente whichprimeiro a saber se existe um executável com o nome do commandName , em seguida, typee commandem seguida, se o commandName não foi encontrado ainda: whence, where, whereis, whatis, hashnessa ordem.


Script de shell para fornecer um whichexecutável.

#! /bin/sh
set -ef; oldIFS=$IFS; IFS=:

say()( IFS=" "; printf "%s\n" "$*"; )
say "Simplified version of which."
usage(){ say Usage: "$0" [-f] args; }
if [ "$#" -eq 0 ]; then say Missing argument(s); usage; exit 2; fi

firstmatch=0
while getopts f whichopts; do
    case "$whichopts" in
        f) firstmatch=1 ;;
        ?) usage; exit 3 ;;
    esac
done
[ "$OPTIND" -gt 1 ] && shift `expr "$OPTIND" - 1`

allret=0; [ "$#" -eq 0 ] && allret=1
for program in "$@"; do
    ret=1
    for element in $PATH''; do
        case "$program" in
            */*) element="$program"; loop=0;;
            *)   element="${element:-.}/$program"; loop=1;;
        esac
        if [ -f "$element" ] && [ -x "$element" ]; then
            say "$element"
            ret=0
            if [ "$firstmatch" -eq 1 ] || [ "$loop" -eq 0 ]; then break; fi
        fi
    done
    [ "$ret" -eq 1 ] && allret=1
done

IFS="$oldIFS"
exit "$allret"

0

Muitas vezes ouvimos o que deve ser evitado. Por quê? O que devemos usar em vez disso?

Eu nunca tinha ouvido aquilo. Forneça exemplos específicos. Eu me preocuparia com sua distribuição Linux e pacotes de software instalados, pois é daí que whichvem!

SLES 11.4 x86-64

na versão tcsh 6.18.01:

> which which

which: shell built-in command.

na versão bash 3.2-147:

> which which

/usr/bin/which

> which -v

GNU which v2.19, Copyright (C) 1999 - 2008 Carlo Wood.
GNU which comes with ABSOLUTELY NO WARRANTY;
This program is free software; your freedom to use, change
and distribute this program is protected by the GPL.

whichfaz parte do util-linux, um pacote padrão distribuído pela Linux Kernel Organization para uso como parte do sistema operacional Linux. Ele também fornece esses outros arquivos

/bin/dmesg
/bin/findmnt
/bin/logger
/bin/lsblk
/bin/more
/bin/mount
/bin/umount
/sbin/adjtimex
/sbin/agetty
/sbin/blkid
/sbin/blockdev
/sbin/cfdisk
/sbin/chcpu
/sbin/ctrlaltdel
/sbin/elvtune
/sbin/fdisk
/sbin/findfs
/sbin/fsck
/sbin/fsck.cramfs
/sbin/fsck.minix
/sbin/fsfreeze
/sbin/fstrim
/sbin/hwclock
/sbin/losetup
/sbin/mkfs
/sbin/mkfs.bfs
/sbin/mkfs.cramfs
/sbin/mkfs.minix
/sbin/mkswap
/sbin/nologin
/sbin/pivot_root
/sbin/raw
/sbin/sfdisk
/sbin/swaplabel
/sbin/swapoff
/sbin/swapon
/sbin/switch_root
/sbin/wipefs
/usr/bin/cal
/usr/bin/chrp-addnote
/usr/bin/chrt
/usr/bin/col
/usr/bin/colcrt
/usr/bin/colrm
/usr/bin/column
/usr/bin/cytune
/usr/bin/ddate
/usr/bin/fallocate
/usr/bin/flock
/usr/bin/getopt
/usr/bin/hexdump
/usr/bin/i386
/usr/bin/ionice
/usr/bin/ipcmk
/usr/bin/ipcrm
/usr/bin/ipcs
/usr/bin/isosize
/usr/bin/line
/usr/bin/linux32
/usr/bin/linux64
/usr/bin/look
/usr/bin/lscpu
/usr/bin/mcookie
/usr/bin/mesg
/usr/bin/mkzimage_cmdline
/usr/bin/namei
/usr/bin/rename
/usr/bin/renice
/usr/bin/rev
/usr/bin/script
/usr/bin/scriptreplay
/usr/bin/setarch
/usr/bin/setsid
/usr/bin/setterm
/usr/bin/tailf
/usr/bin/taskset
/usr/bin/time
/usr/bin/ul
/usr/bin/uname26
/usr/bin/unshare
/usr/bin/uuidgen
/usr/bin/wall
/usr/bin/whereis
/usr/bin/which
/usr/bin/write
/usr/bin/x86_64
/usr/sbin/addpart
/usr/sbin/delpart
/usr/sbin/fdformat
/usr/sbin/flushb
/usr/sbin/freeramdisk
/usr/sbin/klogconsole
/usr/sbin/ldattach
/usr/sbin/partx
/usr/sbin/rcraw
/usr/sbin/readprofile
/usr/sbin/rtcwake
/usr/sbin/setctsid
/usr/sbin/tunelp

my util-linuxé a versão 2.19. As notas de versão podem ser facilmente encontradas na v2.13 de 28 de agosto de 2007. Não tendo certeza de qual era o objetivo ou objetivo disso, certamente não foi respondido naquela coisa demorada, votada 331 vezes.


2
Observe como a pergunta não menciona a que Unix se refere. Linux é apenas um dos poucos.
Kusalananda

2
Como which -vmostra, esse é o GNU que (o extravagante mencionado na outra resposta e não é de forma alguma específico para o Linux), não o util-linux que o AFAIK nunca incluiu um whichutilitário. util-linux 2.19 é de 2011, GNU que 2.19 é de 2008.
Stéphane Chazelas
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.