我已经读过:有没有办法就地修改文件?
我很好奇是否有一种方法可以使用不会创建临时文件的命令来就地修改文件。假设我创建一个目录
mkdir read_only
,然后在 read_only 中创建一些文件。当我高兴地完成文件创建后,我运行chmod 555 read_only
.现在是否可以在不使用临时文件的情况下修改任何文件?具体来说,我想要一个可以通过 bash 脚本完成的解决方案。我不想知道这是否可能;我也在寻求解决方案。到目前为止,我使用 bash/unix 命令管理的所有想法都会创建临时文件。
编辑:我相信我的问题不是重复的是否可以使用“写入”权限来编辑文件,但不能对其父目录进行编辑?出于以下原因:
- 除了问“可以”之外,我还在寻求解决方案。而他们只问“可以”。
- 可能重复的答案并不能完全回答我的问题。我要求提供可以放入 bash 脚本中的命令。
答案1
您需要目录的写权限才能在其中创建或删除文件,但不能写入其中的文件。大多数 shell 命令在给出输出文件时,只需打开该文件进行写入,并替换文件中先前的数据。重定向>
运算符截断现有文件(即删除现有文件内容,导致文件长度为零)并开始写入。重定向>>
运算符使数据附加到文件末尾。
写入文件受到低级接口提供的可能性的限制。您可以就地覆盖文件中的字节、附加到文件末尾以及将文件截断为选定的长度。您不能在向前移动后续字节时插入字节(如foobar
→ fooNEWSTUFFbar
),也不能在向后移动后续字节时删除字节(如foobar
→ for
),除非通过读取要移动的数据并将其写入新位置来模拟这些操作。
就地编辑文件的问题是,如果出现问题(程序错误、磁盘已满、断电等),很难确保文件内容保持一致。这就是为什么稳健的文件处理通常涉及使用新数据写入临时文件,然后将该临时文件移动到位。
就地编辑文件的另一个限制是涉及读取和写入的复杂操作不是原子的。仅当其他任务可能希望在您修改的同时读取该文件时,这才会出现问题。例如,如果您通过读取最后 3 个字节 ( ) 然后写入新数据 ( → ) 并最后附加旧尾部 ( → )foobar
来更改为,则并发读取器可能会看到,甚至文件的其他部分状态仅包含部分写入的数据。同样,首先写入临时文件可以解决此问题,因为将临时文件移动到位是一个原子操作。fooNEWSTUFFbar
bar
foo
fooNEWSTUFF
fooNEWSTUFF
fooNEWSTUFFbar
fooNEWSTUFF
如果您不关心这些限制,那么您可以就地修改文件。附加数据很容易(>>
),大多数其他转换都比较复杂。一个常见的陷阱是
somefilter <somefile >somefile
不适somefilter
用于文件内容:运算符在开始读取>
输出文件之前截断输出文件。somefilter
Joey Hess 的 moreutils包含一个名为sponge
这解决了这个问题。您无需将输出重定向到somefile
,而是通过管道将其输入sponge
,后者会读取其所有输入并然后用读取的输入覆盖现有文件。请注意,如果过滤器失败,仍然可能会得到部分数据。
somefilter <somefile | sponge somefile
如果您没有sponge
,解决此问题的便携式简单方法是首先将数据读入内存:
content=$(cat somefile; echo a)
content=${content%a}
该echo a
位用于保留文件末尾的换行符 - 命令替换总是删除尾随的换行符。然后您可以将内容传递给命令:
printf %s "$content" | somefilter >somefile
这将用过滤器的输出替换文件的内容。如果命令因任何原因失败,文件的原始内容将丢失,并且文件包含命令在失败之前写入的任何数据。
请注意,此方法不适用于二进制文件,因为大多数 shell 不支持空字节。
就地修改文件的另一种方法是使用ed
编辑器,与 sed 非常相似sed
,但将文件加载到内存中并将其保存到位,而不是 sed 的逐行操作。
仅使用标准 shell 工具对文件进行操作而不将其加载到内存中且不创建临时文件会比较棘手,但这是可以完成的。 Shell 重定向和大多数文本处理实用程序仅允许您附加到文件或从头开始覆盖它。您可以用来dd conv=notrunc seek=…
覆盖文件中某个偏移处的数据,而不会影响未被覆盖的部分;看有没有办法就地修改文件?举个例子。
答案2
如果该目录是只读的,但该目录中的文件是读/写的,则没有什么可以阻止您覆盖这些文件。
从脚本中,您可以使用通常的重定向写入文件>
,>>
也可以使用cp
.
您不能做的是在目录中创建一个新文件并在现有文件之上重命名它。创建一个新文件并重命名它通常是可取的,因为重命名是原子发生的。这提供了一些防止数据丢失的保护,并防止任何其他通过常用名称读取文件的进程在更新期间仅看到部分文件。
缺乏创建新文件和重命名的能力意味着您必须非常仔细地考虑更新文件时需要什么保证。
答案3
珀尔的Tie::File
模块提供真正的就地编辑功能:
perl -MTie::File -e '
tie @a,"Tie::File","your_file_here";
# Do something...
'
这使得 的元素@a
进入文件的行,并且对 所做的任何更改@a
都会反映在文件中,即使文件位于只读目录中也是如此。
答案4
不可以。您不能使用 sed 或 perl 的-i
开关来编辑只读目录中的文件。正如您正确假设的那样,您将不会被允许创建必要的临时文件:
$ ls -ld read_only/
dr-xr-xr-x 2 terdon terdon 4096 Apr 13 02:16 read_only/
$ ls -l read_only/file
-rw-r--r-- 1 terdon terdon 3 Apr 13 02:16 read_only/file
$ sed -i 's/a/A/' read_only/file
sed: couldn't open temporary file read_only/sedOEdv8L: Permission denied
$ perl -i -pe 's/a/A/' read_only/file
Can't remove read_only/file: Permission denied, skipping file.
不过,您可以使用 Joseph.R 的漂亮 Perl 技巧:
$ perl -MTie::File -e 'tie @a,"Tie::File","$ARGV[0]"; $a[0]=~s/a/A/' read_only/file