博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用vim保存权限不够的文件
阅读量:5969 次
发布时间:2019-06-19

本文共 6097 字,大约阅读时间需要 20 分钟。

hot3.png

问题描述

今天在根目录下,使用vim编辑器编写了一段程序,但是在保存的时候被提示:E505:“file” is read-only(add ! to override )

强制写入呢?结果是E212: Can't open file for writing.

我们使用vim自带的帮助文档,可以看到

 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 directoryor the file name is not valid.

这是由于没有root权限造成的。

解决方法一(tee)

这时我们需要输入这样一段命令

:w !sudo tee %

解释一下这行命令什么意思?

w的意思

通过在vim下查看w的帮助文档:

                                                        *: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 youwrite the whole buffer, it is no longer considered changed.  When youwrite 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而无需退出。

注意到,

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的意思

通过维基百科知道,如下图所示:

tee的作用

TEE(1)                           User Commands                          TEE(1)NAME       tee - read from standard input and write to standard output and filesSYNOPSIS       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: 
       General help using GNU software: 
       Report tee translation bugs to 
COPYRIGHT       Copyright  ©  2013  Free Software Foundation, Inc.  License GPLv3+: GNU       GPL version 3 or later 
.       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 wellSee ":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 deniedshell 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.

转载于:https://my.oschina.net/donngchao/blog/543424

你可能感兴趣的文章
批处理文件学习笔记
查看>>
[考试]20151008
查看>>
perf-perf stat用户层代码分析
查看>>
OSI七层与TCP/IP五层网络架构详解
查看>>
(转载)equals与==
查看>>
shell
查看>>
Centos防火墙添加IP白名单
查看>>
LeetCode - Backspace String Compare
查看>>
namespace用法
查看>>
MySQL 5.7原生JSON格式支持
查看>>
[吴恩达机器学习笔记]14降维3-4PCA算法原理
查看>>
Solr分词
查看>>
二十四种设计模式:策略模式(Strategy Pattern)
查看>>
统计某个字符串中指定字符串出现的次数
查看>>
asp.net三层结构中,SQL助手类DbHelperSQL
查看>>
scala map和flatMap
查看>>
.Net Core下使用 RSA
查看>>
python 数据库中文乱码 Excel
查看>>
利用console控制台调试php代码
查看>>
递归算法,如何把list中父子类对象递归成树
查看>>