sh
是否有任何好的技术可以构建巨大的自动生成的 shell 脚本,以便即使文件无法容纳在内存中也可以执行它们?另外,是否有任何形式的保证,shell 本身不会将heredoc 一次性全部读入内存?在实践中,哪些 shell 会避免将此处文档存储在内存中,是否可以依靠某个任意机器上的一致性sh
来执行此操作?
我正在阅读有关 GNU 的内容shar
,想知道它是否可以用于太大而无法放入内存的文件。它使用heredocs来存储内容。
sed -e 's/^X//' << 'SHAR_EOF' | uudecode &&
Msome binary content
Xsome text content
SHAR_EOF
然而,有多个这样的heredoc,并且脚本末尾有一些固定的非heredoc内容shar
,可能需要在执行脚本的任何部分之前对其进行解析。如果 shell 没有解析整个脚本,那么在执行第一个命令之前就不可能拒绝格式错误的脚本。
以下是来自 shar 存档的尾随 shell 注释的片段:
...
else
test `LC_ALL=C wc -c < 'a.binary'` -ne 126472 && \
${echo} "restoration warning: size of 'a.binary' is not 126472"
fi
fi
if rm -fr ${lock_dir}
then ${echo} "x - removed lock directory ${lock_dir}."
else ${echo} "x - failed to remove lock directory ${lock_dir}."
exit 1
fi
exit 0
答案1
无法保证 shell 会或不会将整个此处文档加载到内存中。巨大的脚本并不常见,因此 shell 实现者不太可能优化这种情况。甚至有点不可取的是,在执行开始之前整个脚本没有加载到内存中来执行它,但是所有常见的 shell 都会在完全加载之前执行,这意味着如果脚本文件在执行过程中被修改,它们将执行垃圾。
实验上,在 Debian jessie 上,dash、bash、mksh 和 zsh 将 130kB 的此处文档加载到内存中,而 ksh93 则复制 64kB 块而不分配更多内存。因此,使用不适合内存的此处文档的唯一方法是确保您的脚本是使用 ksh93(或者可能是 ksh88)执行的 - 在执行此操作之前,请确保其他版本也是如此,我还没有尚未验证所有 ksh 版本的行为是否相同。
您可以做的更便携的是将所有数据放在脚本的末尾,并用于tail -c $offset
提取有效负载。它在实践中是有效的,因为通常的 shell 都不会在执行脚本之前将其完全加载到内存中。此方法的优点是有效负载可以是二进制的 - 这里文档不能包含 end-of-heredoc 字符串或空字节。
如果您的脚本是不变的,您可以对有效负载的偏移量进行硬编码。如果不是,您可以在脚本末尾放置一个可区分的标记,并使用 awk 来确定其位置:
offset=$(awk '{offset += length($0) + 1}
$0 == "# payload starts here (do not remove this magic comment)" {
print offset; exit
}')
…
tail -c "$offset" <"$0" — …
…
# payload starts here (do not remove this magic comment)
如果您有多个有效负载,则需要更复杂的命令来提取它们。请注意,head -c
并非所有 UNIX 变体中都存在这一点。您可以使用它dd ibs=1 count=$bytes
来实现相同的效果,但它可能会非常慢,因为许多dd
实现一次会复制一个字节。我建议附加一个 tar 存档并从中按名称提取文件。