Linux環(huán)境下VI/VIM編輯文件時無權(quán)限保存的解決方法(普通用戶)
在Linux環(huán)境下,如果直接使用VI/VIM命令編輯沒有修改權(quán)限的文件時,保存的時候就會提示用戶無法進(jìn)行保存操作,一般的解決方法只能是關(guān)閉文件重新以sudo權(quán)限打開該文件編輯后再保存(前提是用戶具有sudo權(quán)限)。其實(shí),在VI/VIM模式下通過一些簡單的命令,就能在不關(guān)閉當(dāng)前文件的情況下達(dá)到保存文件的目的。
方法一
關(guān)于%! sudo tee % > /dev/null這條命令的說明如下
此命令是把當(dāng)前文件(即%)作為stdin傳給sudo tee命令來執(zhí)行。
方法二
在Linux上工作的朋友很可能遇到過這樣一種情況,當(dāng)你用Vim編輯完一個文件時,運(yùn)行:wq保存退出,突然蹦出一個錯誤:
E45: 'readonly' option is set (add ! to override)
這表明文件是只讀的,按照提示,加上!強(qiáng)制保存::w!,結(jié)果又一個錯誤出現(xiàn):
"readonly-file-name" E212: Can't open file for writing
文件明明存在,為何提示無法打開?這錯誤又代表什么呢?查看文檔:help E212:
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.
原來是可能沒有權(quán)限造成的。此時你才想起,這個文件需要root權(quán)限才能編輯,而當(dāng)前登陸的只是普通用戶,在編輯之前你忘了使用sudo來啟動Vim,所以才保存失敗。于是為了防止修改丟失,你只好先把它保存為另外一個臨時文件temp-file-name,然后退出Vim,再運(yùn)行sudo mv temp-file-name readonly-file-name覆蓋原文件。
但這樣操作過于繁瑣。而且如果只是想暫存此文件,還需要接著修改,則希望保留Vim的工作狀態(tài),比如編輯歷史,buffer狀態(tài)等等,該怎么辦?能不能在不退出Vim的情況下獲得root權(quán)限來保存這個文件?
解決方案
答案是可以,執(zhí)行這樣一條命令即可:
:w !sudo tee %
接下來我們來分析這個命令為什么可以工作。首先查看文檔:help :w,向下滾動一點(diǎn)可以看到:
*: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,$)
把這個使用方法對應(yīng)前面的命令,如下所示:
: w !sudo tee %
| | | |
:[range]w[rite] [++opt] !{cmd}
我們并未指定range,參見幫助文檔最下面一行,當(dāng)range未指定時,默認(rèn)情況下是整個文件。此外,這里也沒有指定opt。
Vim中執(zhí)行外部命令
接下來是一個嘆號!,它表示其后面部分是外部命令,即sudo tee %。文檔中說的很清楚,這和直接執(zhí)行:!{cmd}是一樣的效果。后者的作用是打開shell執(zhí)行一個命令,比如,運(yùn)行:!ls,會顯示當(dāng)前工作目錄下的所有文件,這非常有用,任何可以在shell中執(zhí)行的命令都可以在不退出Vim的情況下運(yùn)行,并且可以將結(jié)果讀入到Vim中來。試想,如果你要在Vim中插入當(dāng)前工作路徑或者當(dāng)前工作路徑下的所有文件名,你可以運(yùn)行:
:r !pwd或:r !ls
此時所有的內(nèi)容便被讀入至Vim,而不需要退出Vim,執(zhí)行命令,然后拷貝粘貼至Vim中。有了它,Vim可以自由的操作shell而無需退出。
命令的另一種表示形式
再看前面的文檔:
Execute {cmd} with [range] lines as standard input
所以實(shí)際上這個:w并未真的保存當(dāng)前文件,就像執(zhí)行:w new-file-name時,它將當(dāng)前文件的內(nèi)容保存到另外一個new-file-name的文件中,在這里它相當(dāng)于一個另存為,而不是保存。它將當(dāng)前文檔的內(nèi)容寫到后面cmd的標(biāo)準(zhǔn)輸入中,再來執(zhí)行cmd,所以整個命令可以轉(zhuǎn)換為一個具有相同功能的普通shell命令:
$ cat readonly-file-name | sudo tee %
這樣看起來”正常”些了。其中sudo很好理解,意為切換至root執(zhí)行后面的命令,tee和%是什么呢?
%的意義
我們先來看%,執(zhí)行:help cmdline-special可以看到:
In Ex commands, at places where a file name can be used, the following
characters have a special meaning. These can also be used in the expression
function expand() |expand()|.
% Is replaced with the current file name. *:_%* *c_%*
在執(zhí)行外部命令時,%會擴(kuò)展成當(dāng)前文件名,所以上述的cmd也就成了sudo tee readonly-file-name。此時整個命令即:
$ cat readonly-file-name | sudo tee readonly-file-name
注意:在另外一個地方我們也經(jīng)常用到%,沒錯,替換。但是那里%的作用不一樣,執(zhí)行:help :%查看文檔:
Line numbers may be specified with: *:range* *E14* *{address}*
{number} an absolute line number
...
% equal to 1,$ (the entire file) *:%*
在替換中,%的意義是代表整個文件,而不是文件名。所以對于命令:%s/old/new/g,它表示的是替換整篇文檔中的old為new,而不是把文件名中的old換成new。
tee的作用
現(xiàn)在只剩一個難點(diǎn): tee。它究竟有何用?維基百科上對其有一個詳細(xì)的解釋,你也可以查看man page。下面這幅圖很形象的展示了tee是如何工作的:

ls -l的輸出經(jīng)過管道傳給了tee,后者做了兩件事,首先拷貝一份數(shù)據(jù)到文件file.txt,同時再拷貝一份到其標(biāo)準(zhǔn)輸出。數(shù)據(jù)再次經(jīng)過管道傳給less的標(biāo)準(zhǔn)輸入,所以它在不影響原有管道的基礎(chǔ)上對數(shù)據(jù)作了一份拷貝并保存到文件中??瓷蠄D中間部分,它很像大寫的字母T,給數(shù)據(jù)流動增加了一個分支,tee的名字也由此而來。
現(xiàn)在上面的命令就容易理解了,tee將其標(biāo)準(zhǔn)輸入中的內(nèi)容寫到了readonly-file-name中,從而達(dá)到了更新只讀文件的目的。當(dāng)然這里其實(shí)還有另外一半數(shù)據(jù):tee的標(biāo)準(zhǔn)輸出,但因?yàn)楹竺鏇]有跟其它的命令,所以這份輸出相當(dāng)于被拋棄。當(dāng)然也可以在后面補(bǔ)上> /dev/null,以顯式的丟棄標(biāo)準(zhǔn)輸出,但是這對整個操作沒有影響,而且會增加輸入的字符數(shù),因此只需上述命令即可。
命令執(zhí)行之后
運(yùn)行完上述命令后,會出現(xiàn)下面的提示:
W12: Warning: File "readonly-file-name" has changed and the buffer was changed in Vim as well
See ":help W12" for more info.
[O]K, (L)oad File:
Vim提示文件更新,詢問是確認(rèn)還是重新加載文件。建議直接輸入O,因?yàn)檫@樣可以保留Vim的工作狀態(tài),比如編輯歷史,buffer等,撤消等操作仍然可以繼續(xù)。而如果選擇L,文件會以全新的文件打開,所有的工作狀態(tài)便丟失了,此時無法執(zhí)行撤消,buffer中的內(nèi)容也被清空。
更簡單的方案:映射
上述方式非常完美的解決了文章開始提出的問題,但畢竟命令還是有些長,為了避免每次輸入一長串的命令,可以將它映射為一個簡單的命令加到.vimrc中:
" Allow saving of files as sudo when I forgot to start vim using sudo.
cmap w!! w !sudo tee > /dev/null %
這樣,簡單的運(yùn)行:w!!即可。命令后半部分> /dev/null在前面已經(jīng)解釋過,作用為顯式的丟掉標(biāo)準(zhǔn)輸出的內(nèi)容。
另一種思路
至此,一個比較完美但很tricky的方案已經(jīng)完成。你可能會問,為什么不用下面這樣更常見的命令呢?這不是更容易理解,更簡單一些么?
:w !sudo cat > %
重定向的問題
我們來分析一遍,像前面一樣,它可以被轉(zhuǎn)換為相同功能的shell命令:
$ cat readonly-file-name | sudo cat > %
這條命令看起來一點(diǎn)問題沒有,可一旦運(yùn)行,又會出現(xiàn)另外一個錯誤:
/bin/sh: readonly-file-name: Permission denied
shell returned 1
這是怎么回事?不是明明加了sudo么,為什么還提示說沒有權(quán)限?稍安勿躁,原因在于重定向,它是由shell執(zhí)行的,在一切命令開始之前,shell便會執(zhí)行重定向操作,所以重定向并未受sudo影響,而當(dāng)前的shell本身也是以普通用戶身份啟動,也沒有權(quán)限寫此文件,因此便有了上面的錯誤。
重定向方案
這里介紹了幾種解決重定向無權(quán)限錯誤的方法,當(dāng)然除了tee方案以外,還有一種比較方便的方案:以sudo打開一個shell,然后在該具有root權(quán)限的shell中執(zhí)行含重定向的命令,如:
:w !sudo sh -c 'cat > %'
可是這樣執(zhí)行時,由于單引號的存在,所以在Vim中%并不會展開,它被原封不動的傳給了shell,而在shell中,一個單獨(dú)的%相當(dāng)于nil,所以文件被重定向到了nil,所有內(nèi)容丟失,保存文件失敗。
既然是由于%沒有展開導(dǎo)致的錯誤,那么試著將單引號'換成雙引號"再試一次:
:w !sudo sh -c "cat > %"
成功!這是因?yàn)樵趯⒚顐鞯絪hell去之前,%已經(jīng)被擴(kuò)展為當(dāng)前的文件名。有關(guān)單引號和雙引號的區(qū)別可以參考這里,簡單的說就是單引號會將其內(nèi)部的內(nèi)容原封不動的傳給命令,但是雙引號會展開一些內(nèi)容,比如變量,轉(zhuǎn)義字符等。
當(dāng)然,也可以像前面一樣將它映射為一個簡單的命令并添加到.vimrc中:
" Allow saving of files as sudo when I forgot to start vim using sudo.
cmap w!! w !sudo sh -c "cat > %"
注意:這里不再需要把輸出重定向到/dev/null中。
寫在結(jié)尾
至此,借助Vim強(qiáng)大的靈活性,實(shí)現(xiàn)了兩種方案,可以在以普通用戶啟動的Vim中保存需root權(quán)限的文件。兩者的原理類似,都是利用了Vim可以執(zhí)行外部命令這一特性,區(qū)別在于使用不同的shell命令。如果你還有其它的方案,歡迎給我留言。
(全文完)
feihu
2014.07.30 于 Shenzhen
相關(guān)文章

集成系統(tǒng)級Claw模式! Deepin 官宣發(fā)布 25.1 版本
deepin操作系統(tǒng)發(fā)布了最新的 25.1 版本更新,該版本基于 deepin 25 正式版積累的多輪內(nèi)測成果,在 AI 能力、內(nèi)核版本、桌面環(huán)境、文件管理器以及系統(tǒng)安全等方面進(jìn)行了更新2026-04-13
又一代老硬件退場! Linux 內(nèi)核正式放棄Intel 486 CPU
在過去的幾十年間,CPU 的架構(gòu)已經(jīng)經(jīng)歷了飛速發(fā)展,x86 系列就是其中之一,而 i486 則屬于該系列中的一個,當(dāng)前,i486 的CPU處理器已經(jīng)夠老,從 Linux 7.1 開始將不再有對2026-04-09
趕緊收藏! 全網(wǎng)最全 Linux 命令總結(jié)
我把 Linux 中最常用、最實(shí)用、最常被問到的命令按照實(shí)際使用場景分類整理,方便你快速查閱和記憶,內(nèi)容覆蓋日常運(yùn)維、開發(fā)調(diào)試、性能分析、文件處理、網(wǎng)絡(luò)、安全、系統(tǒng)管2026-04-08
一分鐘內(nèi)檢查Linux服務(wù)器性能? 9個性能檢測常用的基本命令
今天我們來看看Linux系統(tǒng)中用于性能監(jiān)控的一系列命令,這些命令可以快速查看機(jī)器的負(fù)載情況,詳細(xì)請看下文介紹2026-03-18
從零基礎(chǔ)到精通! 適合高級用戶的15款Linux發(fā)行版推薦
Linux作為操作系統(tǒng)領(lǐng)域靈活性和可定制性的基石,提供了大量滿足不同用戶需求的發(fā)行版,今天分享適合高級用戶的15款Linux發(fā)行版2026-03-10
開箱即用? 這4個高手級Linux發(fā)行版遠(yuǎn)沒你想象的那么安全易用
如果你正在糾結(jié)用哪個發(fā)行版?零基礎(chǔ)新手別被“高端”“極客”“聲明式”這些詞沖昏頭腦,先用好用的,再慢慢進(jìn)階2026-03-10
這幾款SSH工具真的夠用了! Linux好用的ssh工具推薦
在Linux上使用SSH,您需要安裝一個SSH客戶端,今天整理找到的8 款 SSH / 終端工具,從免費(fèi)開源到企業(yè)級商用,從輕量化命令行到一站式工具箱,每款都做了介紹與對比,希望能2026-03-09
在Linux系統(tǒng)下有兩種用戶,即高級用戶root,普通用戶,高級用戶root可以在系統(tǒng)中做任何事情,普通用戶僅可在Linux系統(tǒng)中做有限的事情,下面我們就來看看切換方法2026-02-28
揭秘當(dāng)前登錄用戶的身份! Linux中使用logname命令的技巧
logname命令就是這樣一個簡單但強(qiáng)大的工具,它能幫助我們輕松獲取當(dāng)前登錄用戶的用戶名,今天,我們就來深入探索一下這個命令的工作原理、使用方法和最佳實(shí)踐2026-02-26
在 Linux 系統(tǒng)中,DNS 緩存是一種將域名和 IP 地址映射關(guān)系緩存在本地的機(jī)制,可以加快域名解析速度,并減輕 DNS 服務(wù)器的負(fù)載2026-02-26



