剥壳一个衬板以放在文件前面


Answers:


29

下面的hack是一个快速的即席回答,该方法行之有效并获得了很多好评。然后,随着这个问题变得越来越流行和更多的时间流逝,愤怒的人们开始报告说这个问题确实有效,但是奇怪的事情可能会发生,或者根本不起作用,所以一度被人们低估了。好好玩。

该解决方案利用了系统上文件描述符的确切实现,并且由于实现之间的差异很大,因此它的成功完全取决于系统,绝对不可移植,并且即使有非常重要的意义也不应依赖于此。

现在,有了所有这些答案,答案是:


为文件(exec 3<> yourfile)创建另一个文件描述符,然后写入该文件()>&3似乎可以解决对同一文件的读写难题。使用awk为我处理600K文件。但是,使用“ cat”尝试相同的技巧失败。

将前置变量作为变量传递给awk(-v TEXT="$text")可以解决文字引号问题,该问题阻止了使用“ sed”实现此技巧。

#!/bin/bash
text="Hello world
What's up?"

exec 3<> yourfile && awk -v TEXT="$text" 'BEGIN {print TEXT}{print}' yourfile >&3

3
警告:检查输出以确保除第一行外没有其他变化。我使用了此解决方案,尽管它似乎可行,但我注意到似乎添加了一些新行。我不知道这些消息来自何处,所以我不能确定此解决方案确实有问题,但是请当心。
conradlee

1
请注意,在上面的bash示例中,双引号跨越了回车符,以演示前面有多行。检查您的外壳程序和文本编辑器是否协作。\ r \ n浮现在脑海。
约翰·米

4
请谨慎使用!说明为何这样的评论:stackoverflow.com/questions/54365/...
亚历

3
如果现在不可靠,如何用更好的解决方案更新答案?
zakdances

一个黑客是有用的当且仅当精确已知为什么它的工作原理,因此,在什么样的仔细观察约束。尽管您有免责声明,但您的骇客不符合这些条件(“完全取决于系统”,“似乎有待克服”,“为我工作”)。如果我们认真对待您的免责声明,则没有人可以使用您的骇客-我同意。那我们剩下什么呢?嘈杂的分心。我看到两个选项:(1)删除你的答案,或(b)把它变成一个警世故事,这可以解释为什么-诱人的,因为它可能是-这种方法不能正常工作一般
mklement0

102

这仍然使用一个临时文件,但至少它在一行上:

echo "text" | cat - yourfile > /tmp/out && mv /tmp/out yourfile

信用:BASH:在文件前添加文本/行


4
-之后在做什么cat
makbol

有没有一种方法可以添加“文本”而后无需换行?
chishaku 2015年

@chishakuecho -n "text"
Sparhawk

3
警告:如果yourfile是符号链接,则不会执行您想要的操作。
dshepherd

答案是否与此不同:echo“ text”> / tmp / out; cat yourfile >> / tmp / out; mv / tmp / out yourfile ??
理查德


20

约翰·米(John Mee):您的方法不能保证能正常工作,并且如果您将4096字节以上的内容添加到前面,可能会失败(至少gnu awk会发生这种情况,但我想其他实现将具有类似的约束)。在这种情况下,它不仅会失败,而且将进入一个无限循环,在循环中它将读取自己的输出,从而使文件增长,直到所有可用空间都被填满。

自己尝试:

exec 3<>myfile && awk 'BEGIN{for(i=1;i<=1100;i++)print i}{print}' myfile >&3

(警告:过一会儿杀死它,否则它将填满文件系统)

此外,以这种方式编辑文件非常危险,这是非常糟糕的建议,因为似乎在编辑文件时发生了某些情况(崩溃,磁盘已满),几乎可以保证文件处于不一致状态。


6
这可能应该是评论,而不是单独的答案。
lkraav

10
@Ikraav:也许吧,但是“评论”很长而且很详尽(有用),它看起来并不好,而且可能与StackOverflow有限的评论能力不符。
汉迪·爱侣湾

18

没有临时文件是不可能的,但是这里有一个单面纸

{ echo foo; cat oldfile; } > newfile && mv newfile oldfile

您可以使用ed或perl等其他工具来执行此操作,而无需使用临时文件。


16

可能值得注意的是,使用mktemp之类的实用程序安全地生成临时文件通常是一个好主意,至少在使用根特权执行脚本的情况下。例如,您可以执行以下操作(再次以bash进行):

(tmpfile=`mktemp` && { echo "prepended text" | cat - yourfile > $tmpfile && mv $tmpfile yourfile; } )

15

如果需要在您控制的计算机上安装此软件包,请安装软件包“ moreutils”并使用“海绵”。然后,您可以执行以下操作:

cat header myfile | sponge myfile

当与@Vinko Vrsalovic的答案结合使用时,这对我来说效果很好:{ echo "prepended text"; cat myfile } | sponge myfile
Jesse Hallett

13

使用bash heredoc可以避免需要tmp文件:

cat <<-EOF > myfile
  $(echo this is prepended)
  $(cat myfile)
EOF

之所以有效,是因为在执行带重定向的cat之前,在评估bash脚本时会评估$(cat myfile)。


9

假设您要编辑的文件是my.txt

$cat my.txt    
this is the regular file

您要添加的文件是header

$ cat header
this is the header

确保头文件中有最后一个空行。
现在,您可以添加

$cat header <(cat my.txt) > my.txt

你最终得到

$ cat my.txt
this is the header
this is the regular file

据我所知,这仅适用于“ bash”。


为什么这样做?括号是否会使中间的猫在单独的shell中执行并立即转储整个文件?
Catskul

我认为<(cat my.txt)在文件系统中的某个位置创建了一个临时文件。然后在修改原始文件之前读取该文件。
cb0 2010年

曾经有人向我展示了如何使用“ <()”语法。但是,我从未学过如何调用此方法,也无法在bash联机帮助页中找到任何内容。如果有人对此有更多了解,请告诉我。
cb0 2010年

1
没为我工作。不知道为什么。我正在使用OS X Yosemite上的GNU bash 3.2.57(1)-发行版(x86_64-apple-darwin14)。我最后this is the header在my.txt中输入了两行。即使我将Bash更新4.3.42(1)-release为相同的结果。
Kohányi罗伯特

1
Bash不会创建一个临时文件,它会创建一个FIFO(管道),进程替换(<(...))中的命令将写入该FIFO(管道),因此无法保证必须my.txt预先读取所有内容,否则,该技术将无法工作。
mklement0

9

当您开始尝试使用shell脚本进行困难的事情时,我强烈建议您尝试使用“适当的”脚本语言(Python / Perl / Ruby / etc)重写脚本

至于在文件前添加一行,则无法通过管道执行此操作,因为当您执行时cat blah.txt | grep something > blah.txt,它会无意中使文件空白。sponge您可以安装一个小的实用程序命令(您可以安装该命令,该命令cat blah.txt | grep something | sponge blah.txt会缓冲文件的内容,然后将其写入文件中)。它类似于临时文件,但您不必显式地执行该操作。但是我想说,这比Perl更为“糟糕”。

也许有一种方法可以通过awk或类似的方法来完成,但是如果您必须使用shell脚本,我认为临时文件是迄今为止最简单的(/仅?)方法。


7

像Daniel Velkov建议的那样,使用tee。
对我来说,这是简单的智能解决方案:

{ echo foo; cat bar; } | tee bar > /dev/null

7

编辑:这是坏的。在带有cat和tee的文件之前查看奇怪的行为

解决覆盖问题的方法是使用tee

cat header main | tee main > /dev/null

挂了一段时间。以“ tee:main:设备上没有空间”结尾。主要是几个GB,尽管两个原始文本文件都只有几个KB。
Marcos

3
如果您发布的解决方案已损坏(并且实际上已用完用户的硬盘),请帮忙社区并删除您的答案。
aioobe 2014年

1
您能指出一个指导方针,说应该删除错误的答案吗?
Daniel Velkov 2014年

3

我用的那个。这使您可以按照自己喜欢的方式指定顺序,额外的字符等:

echo -e "TEXTFIRSt\n$(< header)\n$(< my.txt)" > my.txt

PS:如果文件包含带反斜杠的文本,则只有它不起作用,因为它会被解释为转义符


3

主要用于娱乐/空壳高尔夫,但

ex -c '0r myheader|x' myfile

可以解决问题,并且没有管道或重定向。当然,vi / ex并非真的非交互使用,因此vi会短暂闪烁。


从我的角度来看,这是最好的配方,因为它使用现有命令,并且以直接的方式执行。
迭戈

2

为什么不简单使用ed命令(如fluffle此处已建议的那样)?

ed将整个文件读入内存并自动执行就地文件编辑!

所以,如果您的文件不是那么大...

# cf. "Editing files with the ed text editor from scripts.",
# http://wiki.bash-hackers.org/doku.php?id=howto:edit-ed

prepend() {
   printf '%s\n' H 1i "${1}" . wq | ed -s "${2}"
}

echo 'Hello, world!' > myfile
prepend 'line to prepend' myfile

另一个变通办法是按照JürgenHötzel在将sed's / c / d /'myFile重定向到myFile的输出重定向中的建议使用打开文件句柄。

echo cat > manipulate.txt
exec 3<manipulate.txt
# Prevent open file from being truncated:
rm manipulate.txt
sed 's/cat/dog/' <&3 > manipulate.txt

当然,所有这些都可以放在一行上。


2

cb0解决方案的一个变体,用于“无临时文件”以添加固定文本:

echo "text to prepend" | cat - file_to_be_modified | ( cat > file_to_be_modified ) 

再次,这依赖于子外壳程序的执行(..),以避免猫拒绝输入和输出相同的文件。

注意:喜欢此解决方案。但是,在我的Mac中,原始文件丢失了(认为它不应该,但是会丢失)。可以通过将解决方案编写为以下内容来解决该问题:echo“ text to prepend” | 猫-file_to_be_modified | 猫> tmp_file; mv tmp_file file_to_be_modified


1
我也发现原始文件丢失了。Ubuntu Lucid。
刺猬2012年


2
sed -i -e '1rmyheader' -e '1{h;d}' -e '2{x;G}' myfile

2

警告:这需要做更多的工作才能满足OP的需求。

尽管有@shixilun的担忧,但应该有一种方法可以使@shixilun的sed方法起作用。将文件读入sed替代字符串时,必须有一个bash命令来转义空格(例如,用'\ n'替换换行符。Shell命令vis并且cat可以处理不可打印的字符,但不能处理空格,因此这不能解决OP的问题问题:

sed -i -e "1s/^/$(cat file_with_header.txt)/" file_to_be_prepended.txt

由于替换脚本中的原始换行而失败,该脚本需要在换行符之前加上换行符(),并可能在后面加上&,以使shell和sed保持满意,就像这样的答案

sed 对于非全局搜索替换命令,其大小限制为40K(模式后无尾随/ g),因此可以避免匿名警告的可怕的awk缓冲区溢出问题。


2
sed -i -e "1s/^/new first line\n/" old_file.txt

正是我在寻找的东西,但确实不是该问题的正确答案
masterxilo

2

使用$(command)可以将命令的输出写入变量。因此,我在一行中的三个命令中执行了此操作,并且没有临时文件。

originalContent=$(cat targetfile) && echo "text to prepend" > targetfile && echo "$originalContent" >> targetfile

1

如果您有一个大文件(在我的情况下只有几百千字节)并且可以访问python,那么这比cat管道解决方案要快得多:

python -c 'f = "filename"; t = open(f).read(); open(f, "w").write("text to prepend " + t)'


1

解决方案printf

new_line='the line you want to add'
target_file='/file you/want to/write to'

printf "%s\n$(cat ${target_file})" "${new_line}" > "${target_file}"

您也可以这样做:

printf "${new_line}\n$(cat ${target_file})" > "${target_file}"

但是在那种情况下,您必须确保没有任何%地方,包括目标文件的内容,因为这可以解释并弄乱您的结果。


2
如果目标文件中有任何内容可以被printf解释为格式化选项,这似乎会爆炸(并截断文件!)。
艾伦H.15年

echo似乎是一个更安全的选择。echo "my new line\n$(cat my/file.txt)" > my/file.txt
艾伦H.15年

@AlanH。我警告答案中的危险。
user137369

实际上,您只警告${new_line}了目标文件中的百分比,而不是目标文件中的百分比
Alan H.

您能说得更清楚些吗?IMO是一个非常大的警告。“任何地方”都不清楚。
艾伦·H.

1

您可以使用perl命令行:

perl -i -0777 -pe 's/^/my_header/' tmp

其中-i将创建文件的内联替换,而-0777将提取整个文件,并使^仅匹配开头。-pe将打印所有行

或者,如果my_header是文件:

perl -i -0777 -pe 's/^/`cat my_header`/e' tmp

/ e将允许替换代码的地方。


0
current=`cat my_file` && echo 'my_string' > my_file && echo $current >> my_file

其中“ my_file”是在“ my_string”之前添加的文件。


0

我最喜欢@fluffle的ed方法。毕竟,任何工具的命令行开关与脚本编辑器命令在本质上都是相同的。没有看到脚本化的编辑器解决方案“整洁度”要小得多或什么都没有。

这是我的一句话,附加到.git/hooks/prepare-commit-msg内部repo .gitmessage文件中以提交消息:

echo -e "1r $PWD/.gitmessage\n.\nw" | ed -s "$1"

范例.gitmessage

# Commit message formatting samples:
#       runlevels: boot +consolekit -zfs-fuse
#

我正在1r代替0r,因为这将在原始模板中的文件顶部保留空的可写行。.gitmessage然后,不要在上面加上空行,最后将得到两条空行。-s抑制ed的诊断信息输出。

关于这一点,我发现对于vim-buffs来说,拥有以下内容也是一件好事:

[core]
        editor = vim -c ':normal gg'

0

变量,ftw?

NEWFILE=$(echo deb http://mirror.csesoc.unsw.edu.au/ubuntu/ $(lsb_release -cs) main universe restricted multiverse && cat /etc/apt/sources.list)
echo "$NEWFILE" | sudo tee /etc/apt/sources.list

0

我认为这是ed最干净的版本:

cat myheader | { echo '0a'; cat ; echo -e ".\nw";} | ed myfile

作为功​​能:

function prepend() { { echo '0a'; cat ; echo -e ".\nw";} | ed $1; }

cat myheader | prepend myfile

0

实际上,如果您使用BASH编写脚本,则可以发出以下命令:

猫-yourfile / tmp / out && mv / tmp / out yourfile

这实际上是您自己在自己的问题中发布的复杂示例。


一位编辑建议将其更改为cat - yourfile <<<"text" > /tmp/out && mv /tmp/out yourfile,但是与我的答案有很大不同,应该是它自己的答案。
蒂姆·肯尼迪

0

恕我直言,没有shell解决方案(并且永远不会是一个解决方案),无论两个文件myheader和的大小如何,它都可以一致且可靠地工作myfile。原因是,如果您要执行此操作,而无需重复使用临时文件(并且不让Shell静默地重复使用临时文件,例如通过exec 3<>myfile,管道传输到tee等结构)。

您正在寻找的“实际”解决方案需要摆弄文件系统,因此它在用户空间中不可用,并且将依赖于平台:您正在要求将使用中的文件系统指针修改为文件系统指针myfile的当前值。对于myheader和替换在文件系统中EOFmyheader一个链接的链接通过指出当前的文件系统地址myfile。这并非易事,而且非超级用户显然也无法做到,而且超级用户也可能无法做到这一点……使用inode,等等。

不过,您可以使用环路设备或多或少地伪造此内容。例如参见此SO线程

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.