Bash shadow um comando - função com o mesmo nome do comando [duplicado]

5

Eu tenho uma função no meu .bashrc para sudo automaticamente para abrir arquivos que não são graváveis por mim:

vim() {
    if [ -w "$1" ]; then
        \vim "$1"
    else
        sudo env HOME="$HOME" \vim -u ~/.vimrc "$1"
    fi
}

Quando o arquivo precisa de sudo, ele funciona bem. Quando isso não acontece, ele chama recursivamente essa função e usa 100% de 1 CPU até I C-C.

A partir desta resposta Existem algumas opções, todas as quais eu tentei. Um realmente funciona:

'vim' "$1" #fails
\vim "$1" #fails
command vim "$1" #Works!

Por que as outras opções não funcionam como eu esperaria?

(Eu sei que isso é uma duplicata, mas foi muito difícil encontrar minha resposta em SO / SE com os títulos das perguntas atuais, então queria postar uma pergunta com um título que eu e outras pessoas poderiam ter encontrado pelo google procura)

    
por jeremysprofile 10.07.2018 / 21:18

3 respostas

6

O problema é que as duas primeiras opções são projetadas apenas para lidar com aliases. Eles não são operadores de redirecionamento especiais que podem perceber se você tem uma função ou comando com o mesmo nome. Tudo o que eles fazem é impedir a expansão, que é o que um alias tenta fazer

alias v='sudo vim'
v x.txt
#automatically expands "v" to "sudo vim" and then does the rest of the command
# v x.txt ==> sudo vim x.txt

Bash apenas tenta expandir a primeira palavra de um comando usando a lista de aliases que conhece (que você pode obter com alias ). Os aliases não aceitam argumentos e só podem ser a primeira palavra (separados por espaços; vim x.txt não será expandido em sudo vimim x.txt usando o alias acima) em um comando para expandir adequadamente.

No entanto, a expansão nunca acontece com aspas simples: echo '$USER' imprimirá literalmente $USER e não o que a variável representa. Além disso, o bash expande \x para x (principalmente). Estas não são formas extra adicionais especificamente para escapar de um alias, elas são apenas parte de como a expansão do bash foi escrita. Assim, você pode usar esses métodos para deixar um alias sombrear um comando e ainda ter acesso ao comando real.

alias ls='ls -la'
ls foo.txt     #will expand into ls -la foo.txt
\ls foo.txt    #will expand into ls foo.txt
'ls' foo.txt   #same as above
'ls foo.txt'   #same as above

No entanto, eles não param as funções. Uma função não precisa de expansão para funcionar, é chamada com o seu nome.

ls () {
    echo "not an alias"
}
alias ls='echo "an alias"'

ls foo.txt          #will echo "an alias"
\ls foo.txt         #will echo "not an alias"
'ls' foo.txt        #will echo "not an alias"
command ls foo.txt  #will actually run 'ls'
    
por 10.07.2018 / 21:18
1

Se você tiver a função, chame o caminho completo para o binário vim , você não obterá o loop recursivo. Além disso, usar a opção -H para o sudo define $ HOME.

vim() {
    if [ -w "$1" ]; then
        command vim "$1"
    else
        sudo env HOME="$HOME" \vim -u ~/.vimrc "$1"
    fi
}

Obrigado. Eu não tinha pensado sobre isso, mas agora eu tenho isso no meu .bashrc também.

EDITADO: chamada atualizada command vim e sudo env em vez de sudo -h . Desta forma, não há loop para ficar.

    
por 10.07.2018 / 23:55
0

Why do the other options not work as I would expect them to?

Porque existem alguns detalhes no modo como uma linha de comando é executada.

O conceito básico é que a primeira palavra de uma linha de comando é o comando que o shell irá procurar e tentar executar, nesta order :

  1. A linha de comando é dividida em metacaracteres (principalmente):

    metacharacters
    A character that, when unquoted, separates words. One of the following:
    | & ; ( ) < > space tab newline

  2. Se a primeira palavra sem aspas corresponder a uma palavra alias, ela será substituída pela definição do alias. Para evitar loops, isso é executado somente once se a nova primeira palavra corresponder a um alias sendo expandido. Isso faz com que isso funcione sem loops:

    $ alias ls='ls -la'
    $ ls dir               # will expand to 'ls -la dir'
    

    Observe que: Se o último caractere do valor do alias for um espaço em branco, a próxima palavra de comando após o alias também será verificada para expansão de alias.

  3. Se a primeira palavra (resultante) for uma expansão (como $var ), ela será substituída (e sujeita à divisão de palavras IFS (e globing) se não for citada):

    $ var='echo -e '
    $ $var test '\twords'            # will expand to 'echo -e test words'
    test    words
    
  4. A primeira palavra resultante após as expansões acima (após as atribuições de variáveis opcionais) será pesquisada nesta ordem:

    • especiais embutidos (somente no modo POSIX)
    • funções
    • builtins
    • comandos hash (leia ajuda hash)
    • comandos externos (pesquisados em $PATH dirs)

    A primeira correspondência encontrada será executada.

O pedido pode ser alterado por (para teste):

  • Para ignorar apenas o alias, use \test ou qualquer outro tipo de expansão.
  • Para ignorar funções e aliases, use command test .
  • Para executar um builtin, use builtin test .
  • Para executar um aplicativo externo, use um caminho explícito: /bin/test .

Então, uma função definida como:

$ ls(){ ls; }

Será chamado em um loop infinito. Como também um script que chama a mesma primeira palavra. Algo como:

#!/bin/bash
$0 "$@"

Será re-executar o mesmo script em um loop até que o kernel quebre (se o kernel em uso tiver um limite de chamadas sucessivas para scripts).

O pedido será exibido executando type -a :

$ test(){ echo test function; }
$ alias test=test
$ type -a
test is aliased to 'test'
test is a function
test ()
{
    echo test function
}
test is a shell builtin
test is /usr/bin/test

A função (como definido na pergunta) só irá ignorar o alias com \vim , mas não a função. Para ignorar alias e funções, use o comando. Esta função deve fazer o que você precisa:

vim() {
        if [ -w "$1" ]; then
            command vim "$1"
        else
            sudo HOME="$HOME" bash -c 'command vim "$@"' _ -u ~/.vimrc "$1"
        fi
      }
    
por 11.07.2018 / 04:42