Como o truque do vim "escreve com sudo" funciona?

1412

Muitos de vocês provavelmente já viram o comando que permite gravar em um arquivo que precisa de permissão root, mesmo quando você esqueceu de abrir o vim com o sudo:

:w !sudo tee %

O problema é que eu não entendo exatamente o que está acontecendo aqui.

Eu já percebi isso: wé para isso

                                                        *:w_c* *:write_c*
:[range]w[rite] [++opt] !{cmd}
                        Execute {cmd} with [range] lines as standard input
                        (note the space in front of the '!').  {cmd} is
                        executed like with ":!{cmd}", any '!' is replaced with
                        the previous command |:!|.

então passa todas as linhas como entrada padrão.

A !sudo teeparte chama teecom privilégios de administrador.

Para que todos façam sentido, o %deve exibir o nome do arquivo (como parâmetro para tee), mas não consigo encontrar referências na ajuda para esse comportamento.

tl; dr Alguém poderia me ajudar a dissecar esse comando?

Sósia
fonte
5
@ Nathan: :w !sudo cat > %Não funcionaria tão bem e não poluiria a saída padrão?
Bjarke Freund-Hansen
51
@ bjarkef - não, isso não funciona. Nesse caso, sudoé aplicado a cat, mas não a >, portanto, não é permitido. Você pode tentar executar o comando inteiro em um subshell sudo, como :w !sudo sh -c "cat % > yams.txt", mas isso também não funcionará, porque no subshell, %é nulo; você esvaziará o conteúdo do seu arquivo.
Nathan Long
3
Eu só quero acrescentar que, depois de digitar esse comando, uma mensagem de aviso pode aparecer. Nesse caso, pressione L. Em seguida, você será solicitado a pressionar enter. Faça e você finalmente terá seu arquivo salvo.
pablofiumara
9
@NathanLong @knittl: :w !sudo sh -c "cat >%"realmente funciona tão bem quanto sudo tee %porque o Vim substitui o nome do arquivo %antes que ele chegue ao subshell. No entanto, nenhum deles funcionará se o nome do arquivo tiver espaços; você tem que fazer :w !sudo sh -c "cat >'%'"ou :w !sudo tee "%"consertar isso.
Han Seoul-Oh
1
Salve usando: W e recarregue o arquivo: command W: execute ': silent w! Sudo tee%> / dev / null' | :editar!
Diego Roberto Dos Santos

Respostas:

1612

Em :w !sudo tee %...

% significa "o arquivo atual"

Como apontado por eugene , %na verdade significa "o nome do arquivo atual", que é passado para teeque ele saiba qual arquivo substituir.

(Nos comandos de substituição, é um pouco diferente; como :help :%mostra, é equal to 1,$ (the entire file)(obrigado a @Orafu por apontar que isso não é avaliado pelo nome do arquivo). Por exemplo, :%s/foo/barsignifica " no arquivo atual , substitua as ocorrências foopor bar". Se você destacar algum texto antes de digitar :s, você verá que as linhas destacadas %substituem seu intervalo de substituição.)

:w não está atualizando seu arquivo

Uma parte confusa desse truque é que você pode pensar que :westá modificando seu arquivo, mas não está. Se você abriu e modificou file1.txt, em seguida, executou :w file2.txt, seria um "salvar como"; file1.txtnão seria modificado, mas o conteúdo atual do buffer seria enviado para file2.txt.

Em vez de file2.txt, você pode substituir um comando do shell para receber o conteúdo do buffer . Por exemplo, :w !catapenas exibirá o conteúdo.

Se o Vim não foi executado com acesso ao sudo, ele :wnão pode modificar um arquivo protegido, mas se ele passar o conteúdo do buffer para o shell, um comando no shell poderá ser executado com o sudo . Neste caso, usamos tee.

T compreensivo

Quanto a tee, imagine o teecomando como um tubo em forma de T em uma situação de tubulação bash normal: ele direciona a saída para o (s) arquivo (s) especificado (s) e também o envia para a saída padrão , que pode ser capturada pelo próximo comando de tubulação.

Por exemplo, em ps -ax | tee processes.txt | grep 'foo', a lista de processos será gravada em um arquivo de texto e repassada para grep.

     +-----------+    tee     +------------+
     |           |  --------  |            |
     | ps -ax    |  --------  | grep 'foo' |
     |           |     ||     |            |
     +-----------+     ||     +------------+
                       ||   
               +---------------+
               |               |
               | processes.txt |
               |               |
               +---------------+

(Diagrama criado com o Asciiflow .)

Veja a teepágina de manual para mais informações.

Tee como um hack

Na situação que sua pergunta descreve, o uso teeé um truque, porque estamos ignorando metade do que ele faz . sudo teegrava em nosso arquivo e também envia o conteúdo do buffer para a saída padrão, mas ignoramos a saída padrão . Não precisamos passar nada para outro comando canalizado neste caso; estamos apenas usando teecomo uma maneira alternativa de escrever um arquivo e para que possamos chamá-lo com sudo.

Tornando este truque fácil

Você pode adicionar isso ao seu .vimrcpara tornar esse truque fácil de usar: basta digitar :w!!.

" Allow saving of files as sudo when I forgot to start vim using sudo.
cmap w!! w !sudo tee > /dev/null %

A > /dev/nullparte joga explicitamente fora a saída padrão, pois, como eu disse, não precisamos passar nada para outro comando canalizado.

Nathan Long
fonte
147
Gosto especialmente da sua notação "w !!" que é tão fácil de lembrar depois de usar "sudo !!" na linha de comando.
Aidan Kane
11
Portanto, isso usa teesua capacidade de gravar stdin em um arquivo. Estou surpreso que não exista um programa cujo trabalho seja fazer isso (encontrei um programa que nunca ouvi falar spongeque faz isso). Eu acho que o típico "gravar um fluxo em um arquivo" é executado por um shell interno. O Vim !{cmd}não bifurca uma concha (bifurcando cmd)? Talvez algo mais óbvio seja usar alguma variante de trabalho em sh -c ">"vez de tee.
Steven Lu
2
@ Steven Lu: spongefaz parte do moreutilspacote em praticamente todas as distribuições, exceto nas distros baseadas no Debian. moreutilstem algumas ferramentas bastante agradáveis ​​que estão a par de ferramentas mais comuns como xargse tee.
Swiss
10
Como expandir esse alias para também informar ao vim para carregar automaticamente o conteúdo do arquivo alterado no buffer atual? Ele me pede, como automatizá-lo?
Zlatko
10
@ user247077: Nesse caso, caté executado como root e a saída é redirecionada pelo shell, que não é executado como root. É o mesmo que echo hi > /read/only/file.
WhyNotHugo 23/05
99

Na linha de comando executada, %representa o nome do arquivo atual . Isso está documentado em :help cmdline-special:

In Ex commands, at places where a file name can be used, the following
characters have a special meaning.
        %       Is replaced with the current file name.

Como você já descobriu, :w !cmdcanaliza o conteúdo do buffer atual para outro comando. O que teefaz é copiar a entrada padrão para um ou mais arquivos e também para a saída padrão. Portanto, :w !sudo tee % > /dev/nullefetivamente grava o conteúdo do buffer atual no arquivo atual enquanto é root . Outro comando que pode ser usado para isso é dd:

:w !sudo dd of=% > /dev/null

Como atalho, você pode adicionar esse mapeamento ao seu .vimrc:

" Force saving files that require root permission 
cnoremap w!! w !sudo tee > /dev/null %

Com o exposto acima, você pode digitar :w!!<Enter>para salvar o arquivo como root.

Eugene Yarmash
fonte
1
Interessante, :help _%mostra o que você digitou, mas :help %mostra a chave de correspondência de chaves. Eu não teria pensado em tentar o prefixo de sublinhado, isso é algum tipo de padrão na documentação do vim? Existem outras coisas "especiais" para tentar ao procurar ajuda?
David Pope
2
@ David: O helpcomando salta para uma tag. Você pode ver as tags disponíveis com :h help-tags. Você também pode usar a conclusão de linha de comando para ver as tags correspondentes: :h cmdline<Ctrl-D>(ou :h cmdline<Tab>se você definir wildmodeem conformidade)
Eugene Yarmash
1
Eu tive que usar cmap w!! w !sudo tee % > /dev/nullno meu arquivo .vimrc para fazer isso funcionar. O %extraviado está na resposta acima? (Nenhum especialista vim aqui.)
dmmfll
@DMfll Sim, é. O comando na resposta resultaria no sudo tee > /dev/null /path/to/current/filequal realmente não faz sentido. (Indo para editar isso)
jazzpi
1
@jazzpi: Você está errado. Os shells na verdade não se importam onde, na linha de comando, você faz o redirecionamento de arquivo.
Eugene Yarmash
19

Isso também funciona bem:

:w !sudo sh -c "cat > %"

Isso é inspirado no comentário de @Nathan Long.

AVISO :

"deve ser usado em vez de 'porque queremos %ser expandidos antes de passar para o shell.

feihu
fonte
11
Embora isso possa funcionar, também fornece ao sudo acesso a vários programas (sh e cat). Os outros exemplos podem ser mais seguros, substituindo teepor /usr/bin/teepara impedir ataques de modificação de PATH.
Idbrii 7/09/14
18

:w - Escreva um arquivo.

!sudo - Chame o comando shell sudo.

tee- A saída do comando write (vim: w) redirecionada usando tee. O% não passa de um nome de arquivo atual, ou seja, /etc/apache2/conf.d/mediawiki.conf. Em outras palavras, o comando tee é executado como root e recebe a entrada padrão e a grava em um arquivo representado por%. No entanto, isso solicitará o recarregamento do arquivo novamente (pressione L para carregar as alterações no próprio vim):

link do tutorial

kev
fonte
9

A resposta aceita cobre tudo, então vou dar outro exemplo de atalho que eu uso, para o registro.

Adicione-o ao seu etc/vim/vimrc(ou ~/.vimrc):

  • cnoremap w!! execute 'silent! write !sudo tee % >/dev/null' <bar> edit!

Onde:

  • cnoremap: informa ao vim que o seguinte atalho deve ser associado na linha de comando.
  • w!!: o próprio atalho.
  • execute '...': um comando que executa a seguinte sequência.
  • silent!: execute silenciosamente
  • write !sudo tee % >/dev/null: a pergunta OP, adicionou um redirecionamento de mensagens NULLpara fazer um comando limpo
  • <bar> edit!: esse truque é a cereja do bolo: ele também chama o editcomando para recarregar o buffer e evitar mensagens como a alteração do buffer . <bar>é como escrever o símbolo de barra vertical para separar dois comandos aqui.

Espero que ajude. Veja também para outros problemas:

Dr Beco
fonte
calado! desativou o prompt de senha para que você não o veja #
Andy Ray
7

Gostaria de sugerir outra abordagem para "Oups que esqueci de escrever sudoao abrir meu arquivo" :

Em vez de receber um permission deniede ter que digitar :w!!, acho mais elegante ter um vimcomando condicional que sudo vimse o proprietário do arquivo for root.

Isso é tão fácil de implementar (pode haver implementações ainda mais elegantes, eu claramente não sou um guru do bash):

function vim(){
  OWNER=$(stat -c '%U' $1)
  if [[ "$OWNER" == "root" ]]; then
    sudo /usr/bin/vim $*;
  else
    /usr/bin/vim $*;
  fi
}

E funciona muito bem.

Essa é uma bashabordagem mais centrada do que umavim para que nem todos gostem.

Claro:

  • há casos de uso em que ele falhará (quando o proprietário do arquivo não for, rootmas exigirsudo , mas a função pode ser editada de qualquer maneira)
  • não faz sentido ao usar vimsomente um arquivo para leitura (no que me diz respeito, uso tailou catpara arquivos pequenos)

Mas acho que isso traz uma experiência muito melhor para o desenvolvedor , que é algo que o IMHO tende a ser esquecido ao usar bash. :-)

Augustin Riedinger
fonte
5
Esteja ciente de que isso é muito menos tolerante. Pessoalmente, a maioria dos meus erros são estúpidos. Então, eu prefiro ficar de olho quando estou fazendo algo que pode ser estúpido e consequente. Obviamente, isso é uma questão de preferência, mas a escalada de privilégios deve ser um ato de consciência. Além disso: se você experimenta isso com tanta frequência que cria ": w !!" um incômodo suficiente para silenciar automaticamente o sudo (mas somente se o proprietário = root); convém examinar seu fluxo de trabalho atual.
Payne
Comentário interessante, embora se deva estar consciente, porque quando o arquivo está sendo aberto, roota senha é consultada.
Augustin Riedinger 29/11
Ah! Há a diferença. Depende se o usuário sudo tiver "NOPASSWD" configurado ou não.
Payne
Em seguida, ter NOPASSWD é o que é menos indulgente ... :)
Augustin Riedinger
4

PARA NEOVIM

Devido a problemas com chamadas interativas ( https://github.com/neovim/neovim/issues/1716 ), estou usando isso para o neovim, com base na resposta do Dr. Beco:

cnoremap w!! execute 'silent! write !SUDO_ASKPASS=`which ssh-askpass` sudo tee % >/dev/null' <bar> edit!

Isso abrirá uma caixa de diálogo usando ssh-askpassa senha do sudo.

dbranco
fonte