今天在根目錄下,使用vim編輯器編寫了一段程序,可是在保存的時候被提示:E505:「file」 is read-only(add ! to override )html
強制寫入呢?結果是E212: Can't open file for writing.redis
咱們使用vim自帶的幫助文檔,能夠看到shell
Cannot open "{filename}" for writing Can't open file for writing For some reason the file you are writing to cannot be created or overwritten. The reason could be that you do not have permission to write in the directory or the file name is not valid.
這是因爲沒有root權限形成的。vim
這時咱們須要輸入這樣一段命令bash
:w !sudo tee %
解釋一下這行命令什麼意思?app
經過在vim下查看w的幫助文檔:less
*: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 |:!|. The default [range] for the ":w" command is the whole buffer (1,$). If you write the whole buffer, it is no longer considered changed. When you write it to a different file with ":w somefile" it depends on the "+" flag in 'cpoptions'. When included, the write command will reset the 'modified' flag, even though the buffer itself may still be different from its file.
接下來是一個歎號!
,它表示其後面部分是外部命令,即sudo tee %
。文檔中說的很清楚,這和直接執行:!{cmd}
是同樣的效果。後者的做用是打開shell執行一個命令,好比,運行:!ls
,會顯示當前工做目錄下的全部文件,這很是有用,任何能夠在shell中執行的命令均可以在不退出Vim的狀況下運行,而且能夠將結果讀入到Vim中來。試想,若是你要在Vim中插入當前工做路徑或者當前工做路徑下的全部文件名,你能夠運行:編輯器
:r !pwd or :r !ls
此時全部的內容便被讀入至Vim,而不須要退出Vim,執行命令,而後拷貝粘貼至Vim中。有了它,Vim能夠自由的操做shell而無需退出。ide
注意到,this
Execute {cmd} with [range] lines as standard input
因此實際上這個:w
並未真的保存當前文件,就像執行:w new-file-name
時,它將當前文件的內容保存到另一個new-file-name
的文件中,在這裏它至關於一個另存爲,而不是保存。它將當前文檔的內容寫到後面cmd
的標準輸入中,再來執行cmd
,因此整個命令能夠轉換爲一個具備相同功能的普通shell命令:
$ cat readonly-file-name | sudo tee %
咱們在vim中執行,:help cmdline-special
% Is replaced with the current file name. *:_%* *c_%*
因此,咱們前面提到的那個命令能夠等價於
$ cat readonly-file-name | sudo tee readonly-file-name
還有一個%,與這裏提到的%意義不一樣,在vim中,:help :%
% equal to 1,$ (the entire file) *:%*
在替換中,%
的意義是表明整個文件,而不是文件名。因此對於命令:%s/old/new/g
,它表示的是替換整篇文檔中的old爲new,而不是把文件名中的old換成new。
經過維基百科知道,以下圖所示:
TEE(1) User Commands TEE(1) NAME tee - read from standard input and write to standard output and files SYNOPSIS tee [OPTION]... [FILE]... DESCRIPTION Copy standard input to each FILE, and also to standard output. -a, --append append to the given FILEs, do not overwrite -i, --ignore-interrupts ignore interrupt signals --help display this help and exit --version output version information and exit If a FILE is -, copy again to standard output. AUTHOR Written by Mike Parker, Richard M. Stallman, and David MacKenzie. REPORTING BUGS Report tee bugs to bug-coreutils@gnu.org GNU coreutils home page: <http://www.gnu.org/software/coreutils/> General help using GNU software: <http://www.gnu.org/gethelp/> Report tee translation bugs to <http://translationproject.org/team/> COPYRIGHT Copyright © 2013 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. SEE ALSO The full documentation for tee is maintained as a Texinfo manual. If the info and tee programs are properly installed at your site, the com‐ mand info coreutils 'tee invocation' should give you access to the complete manual. GNU coreutils 8.21 January 2015 TEE(1) ~
ls -l
的輸出通過管道傳給了tee
,後者作了兩件事,首先拷貝一份數據到文件file.txt
,同時再拷貝一份到其標準輸出。數據再次通過管道傳給less
的標準輸入,因此它在不影響原有管道的基礎上對數據做了一份拷貝並保存到文件中。看上圖中間部分,它很像大寫的字母T,給數據流動增長了一個分支,tee
的名字也由此而來。
如今上面的命令就容易理解了,tee
將其標準輸入中的內容寫到了readonly-file-name
中,從而達到了更新只讀文件的目的。固然這裏其實還有另一半數據:tee
的標準輸出,但由於後面沒有跟其它的命令,因此這份輸出至關於被拋棄。固然也能夠在後面補上> /dev/null
,以顯式的丟棄標準輸出,可是這對整個操做沒有影響,並且會增長輸入的字符數,所以只需上述命令便可。
命令執行完以後,出現以下提示:
W12: Warning: File "picdownloader.py" has changed and the buffer was changed in Vim as well See ":help W12" for more info.
Vim提示文件更新,詢問是確認仍是從新加載文件。建議直接輸入O
,由於這樣能夠保留Vim的工做狀態,好比編輯歷史,buffer等,撤消等操做仍然能夠繼續。而若是選擇L
,文件會以全新的文件打開,全部的工做狀態便丟失了,此時沒法執行撤消,buffer中的內容也被清空。
上述方式很是完美的解決了文章開始提出的問題,但畢竟命令仍是有些長,爲了不每次輸入一長串的命令,能夠將它映射爲一個簡單的命令加到.vimrc
中:
1 " Allow saving of files as sudo when I forgot to start vim using sudo. 2 cmap w!! w !sudo tee > /dev/null %
這樣,簡單的運行:w!!
便可。命令後半部分> /dev/null
在前面已經解釋過,做用爲顯式的丟掉標準輸出的內容。
也可使用另一種方式來解決這個問題:
:w !sudo cat > %
咱們來分析一遍,像前面同樣,它能夠被轉換爲相同功能的shell命令:
$ cat readonly-file-name | sudo cat > %
結果出現的錯誤是:
:w !sudo cat > % /bin/bash: picdownloader.py: Permission denied shell returned 1
這是怎麼回事?不是明明加了sudo
麼,爲何還提示說沒有權限?緣由在於重定向,它是由shell執行的,在一切命令開始以前,shell便會執行重定向操做,因此重定向並未受sudo
影響,而當前的shell自己也是以普通用戶身份啓動,也沒有權限寫此文件,所以便有了上面的錯誤。
咱們介紹了幾種解決重定向無權限錯誤的方法,固然除了tee
方案之外,還有一種比較方便的方案:以sudo
打開一個shell,而後在該具備root權限的shell中執行含重定向的命令,如:
:w !sudo sh -c 'cat > %'
但是這樣執行時,因爲單引號的存在,因此在Vim中%
並不會展開,它被原封不動的傳給了shell,而在shell中,一個單獨的%
至關於nil
,因此文件被重定向到了nil
,全部內容丟失,保存文件失敗。
既然是因爲%
沒有展開致使的錯誤,那麼試着將單引號'
換成雙引號"
再試一次:
:w !sudo sh -c "cat > %"
此次命令的執行成功了。這是由於在將命令傳到shell去以前,%
已經被擴展爲當前的文件名。簡單的說就是單引號會將其內部的內容原封不動的傳給命令,可是雙引號會展開一些內容,好比變量,轉義字符等。
固然,也能夠像前面同樣將它映射爲一個簡單的命令並添加到.vimrc中:
1 " Allow saving of files as sudo when I forgot to start vim using sudo. 2 cmap w!! w !sudo sh -c "cat > %"
注意:這裏再也不須要把輸出重定向到/dev/null
中。
至此,藉助Vim強大的靈活性,實現了兩種方案,能夠在以普通用戶啓動的Vim中保存需root權限的文件。二者的原理相似,都是利用了Vim能夠執行外部命令這一特性,區別在於使用不一樣的shell命令。注意cat的意思:
在man page中,cat的意思是:
cat - concatenate files and print on the standard output
sh -c的意思是從字符串讀取命令:
-c Read commands from the command_string operand instead of from the standard input. Special parameter 0 will be set from the command_name op‐ erand and the positional parameters ($1, $2, etc.) set from the remaining argument operands.