取消一行程序以预置文件

这可能是 一个复杂的解决方案

我正在寻找一个简单的运算符,如“ > >”,但预先。

我恐怕它不存在。我将不得不做一些像

mv myfile tmp
cat myheader tmp > myfile

还有更聪明的吗?

63411 次浏览

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

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

提供者: BASH: 将文本/行添加到文件

没有临时文件是不可能的,但这里有一条线

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

您可以使用 ed 或 perl 等其他工具在不使用临时文件的情况下执行此操作。

实际上,如果你使用的是 BASH 脚本,你可以直接发出:

cat - yourfile  /tmp/out && mv /tmp/out yourfile

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

值得注意的是,使用 Mktemp这样的实用程序安全地生成临时文件通常是 好主意,至少在脚本使用 root 特权执行时是如此。例如,您可以执行以下操作(同样在 bash 中) :

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

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

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

可能有一种方法可以通过 awk 或类似的方法来实现,但是如果必须使用 shell 脚本,我认为临时文件是目前为止最简单的(/only?)方式。.

下面的 黑客是一个快速的即兴答案,工作和收到了很多赞扬。然后,随着这个问题越来越流行,时间越来越长,人们开始报告说,它有点工作,但奇怪的事情可能会发生,或者它根本不工作。真有趣。

我推荐使用 用户222发布的“海绵”解决方案,因为海绵是“ Moreutils”的一部分,可能默认在你的系统上。 (echo 'foo' && cat yourfile) | sponge yourfile

下面的解决方案利用了文件描述符在系统上的确切实现,因为实现在不同的 nix 之间差异很大,所以它的成功完全依赖于系统,绝对不可移植,甚至不应该依赖于任何不太重要的东西。海绵使用/tmp 文件系统,但是将任务压缩为一个命令。

现在,所有这些都已经过去了,最初的答案是:


为文件创建另一个文件描述符(exec 3<> yourfile) ,然后写入该文件描述符(>&3) ,这似乎克服了在同一个文件上读/写的困境。跟 awk 一起处理60万的文件。然而,尝试同样的技巧使用’猫’失败。

将前缀作为变量传递给 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

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

$cat my.txt
this is the regular file

您要预置的文件是头文件

$ 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”中有效。

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

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

这同样依赖于子 shell 执行-(。.)- 以避免 cat 拒绝输入和输出相同的文件。

注意: 喜欢这个解决方案。然而,在我的 Mac 中,原始文件丢失了(我认为不应该丢失,但它确实丢失了)。这可以通过写下你的解决方案来解决: 回显“ text to prepend”| cat-file _ to _ be _ Amendment | cat > tmp _ file; mv tmp _ file _ to _ be _ Amendment

呸! 没人愿意提 战术小组

endor@grid ~ $ tac --help
Usage: tac [OPTION]... [FILE]...
Write each FILE to standard output, last line first.
With no FILE, or when FILE is -, read standard input.


Mandatory arguments to long options are mandatory for short options too.
-b, --before             attach the separator before instead of after
-r, --regex              interpret the separator as a regular expression
-s, --separator=STRING   use STRING as the separator instead of newline
--help     display this help and exit
--version  output version information and exit


Report tac 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 tac translation bugs to <http://translationproject.org/team/>
current=`cat my_file` && echo 'my_string' > my_file && echo $current >> my_file

其中“ my _ file”是要在“ my _ string”前面加上的文件。

John Mee: 您的方法不能保证能够工作,如果预先准备了超过4096字节的内容(至少 gnu awk 是这样的,但我想其他实现也会有类似的约束) ,那么方法可能会失败。在这种情况下,它不仅会失败,而且还会进入一个无止境的循环,在这个循环中它将读取自己的输出,从而使文件不断增长,直到所有可用空间都被填满。

你自己试试看:

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

(警告: 一段时间后杀死它,否则它将填充文件系统)

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

echo '0a
your text here
.
w' | ed some_file

艾德是标准编辑器

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

编辑: 这个坏了。参见 用 cat 和 tee 预置文件时的奇怪行为

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

cat header main | tee main > /dev/null

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

尽管施希伦心存疑虑,但应该有办法让 sed 方法奏效。在将文件读入 sed 替换字符串时,必须有一个 bash 命令来转义空格(例如,用’n’替换换行字符)。Shell 命令 viscat可以处理不可打印的字符,但不能处理空格,所以这并不能解决 OP 的问题:

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

由于替代脚本中的原始换行符失败,需要在换行符前面加上一个行继续字符() ,后面可能加上一个 & ,以保持 shell 和 sed 的正常运行,如 这么回答

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

为什么不简单地使用 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ürgen Hötzel 在 将 sed/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

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

我用的那个。这个命令允许你按照你喜欢的方式指定顺序、额外的字符等:

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

附言: 只是如果文件包含带反斜杠的文本,它就不能工作,因为它会被解释为转义字符

我最喜欢 @ fluffle’s < strong > ed 方法。毕竟,任何工具的命令行切换与脚本编辑器命令在这里基本上是相同的; 没有看到脚本编辑器解决方案的“清洁性”有任何差别或诸如此类的东西。

下面是我在 .git/hooks/prepare-commit-msg后面附加的一行程序,用来预置一个 in-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 爱好者来说,拥有:

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

变量 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

以下是我的发现:

echo -e "header \n$(cat file)" >file

就像 Daniel Velkov 建议的,用 T 恤。
对我来说,这是个简单明智的解决方案:

{ echo foo; cat bar; } | tee bar > /dev/null
sed -i -e '1rmyheader' -e '1{h;d}' -e '2{x;G}' myfile

我认为这是教育最干净的变化:

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

作为一种功能:

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


cat myheader | prepend myfile

如果你需要在你控制的电脑上使用这个软件,安装“ Moreutils”软件包并使用“海绵”,然后你就可以做:

cat header myfile | sponge myfile

主要是为了好玩的贝壳高尔夫,但是

ex -c '0r myheader|x' myfile

将会奏效,而且没有管道或者重定向。当然,vi/ex 并不是真正用于非交互式用途,因此 vi 会短暂地闪现出来。

使用 $(命令),您可以将命令的输出写入变量。 所以我用三个命令在一行中完成,没有临时文件。

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

恕我直言,无论两个文件 myheadermyfile的大小如何,都没有一个能够一致可靠地工作的 shell 解决方案(也永远不会是一个)。原因是,如果你想做到这一点,而不是重复到一个临时文件(并且不让 shell 默默地重复到一个临时文件,例如通过构造,如 exec 3<>myfile,管道到 tee,等等。

您正在寻找的“真正的”解决方案需要修改文件系统,因此它在用户空间中是不可用的,并且将依赖于平台: 您要求将 myfile使用的文件系统指针修改为 myheader的文件系统指针的当前值,并在文件系统中将 myheaderEOF替换为指向 myfile所指向的当前文件系统地址的链接。这并非易事,显然不能由非超级用户完成,而且可能也不能由超级用户完成... ... 使用 inode 等等。

不过,您可以使用循环设备或多或少地伪造这一点。

如果您有一个大文件(在我的例子中是几百 KB)并且访问 python,那么这比 cat管道解决方案快得多:

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

使用 bash herdoc 可以避免使用 tmp 文件:

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

这是因为在执行带有重定向的 cat 之前,在计算 bash 脚本时计算 $(cat myfile)。

快速和肮脏,用 python 缓冲内存中的所有内容:

$ echo two > file
$ echo one | python -c "import sys; f=open(sys.argv[1]).read(); open(sys.argv[1],'w').write(sys.stdin.read()+f)" file
$ cat file
one
two
$ # or creating a shortcut...
$ alias prepend='python -c "import sys; f=open(sys.argv[1]).read(); open(sys.argv[1],\"w\").write(sys.stdin.read()+f)"'
$ echo zero | prepend file
$ cat file
zero
one
two

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}"

但是在这种情况下,您必须确保在任何地方都没有任何 %,包括目标文件的内容,因为这可能会被解释,并且会搞砸您的结果。

您可以使用 perl 命令行:

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

在哪里-我将创建一个内联替换的文件和 -0777将清除整个文件,并使 ^ match 只出现在开始部分。 - PE 会打印所有的行

或者如果 my _ header 是一个文件:

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

其中/e 将允许在替换中对代码进行 eval。

破折号/灰分:

echo "hello\n$(cat myfile)" > myfile

例如:

$ echo "line" > myfile
$ cat myfile
line
$ echo "line1\n$(cat myfile)" > myfile
$ cat myfile
line1
line

我找到的最简单的解决办法是:

cat myHeader myFile | tee myFile

或者

echo "<line to add>" | cat - myFile | tee myFile

备注:

  • 如果只想附加一段文本(而不是一行) ,请使用 echo -n
  • 如果您不想看到输出(生成的文件) ,请将 &> /dev/null添加到末尾。
  • 这可以用来将一个 shebang 附加到文件中。示例:
    # make it executable (use u+x to allow only current user)
    chmod +x cropImage.ts
    # append the shebang
    echo '#''!'/usr/bin/env ts-node | cat - cropImage.ts | tee cropImage.ts &> /dev/null
    # execute it
    ./cropImage.ts myImage.png