Shell编程,避免使用临时文件


8

我经常编写遵循相同模式的KSH Shell脚本:

  • (1)从一个或多个命令检索输出
  • (2)使用grep | cut | awk | sed格式化并打印到屏幕或文件中

为了做到这一点,我经常将(1)的输出存储在一个临时文件中,然后对该文件进行(2)的格式化。

以该代码为例:

TMPFILE=file.tmp

# If tmpfile exists rm it.
[ -f $TMPFILE ] && rm -f $TMPFILE

for SERVICE in $(myfunc); do
    getInfo $SERVICE > $TMPFILE # Store raw output in the TMPFILE

    # I retrieve the relevant data from the TMPFILE
    SERV_NAME=$(head -1 $TMPFILE | sed -e 's/ $//')
    SERV_HOSTNAME=$(grep HOSTNAME $TMPFILE | cut -d "=" -f2)
    SERV_ARGS=$(grep Arguments $TMPFILE | cut -d ":" -f2)

    print $SERV_NAME $SEP $SERV_HOSTNAME $SEP $SERV_ARGS
    rm -f $TMPFILE #rm the TMPFILE in vue of next iteration
done

有没有办法使用管道,重定向和其他方法避免每次都将文件写入磁盘?

如果有帮助,我正在使用ksh版本M-11 / 16 / 88i


3
这是避免在shell脚本中使用ALL_CAPS变量名,并将该命名空间视为shell保留的一种好形式,以避免破坏重要内容,例如PATH其他shell或环境变量。 TMPFILE也许很好,但是TMPDIR很特殊,那么您真的要走那条绳索吗?
2012年

为了后代:被标记为与该unix.stackexchange.com/questions/63923/…重复的另一个问题包括一个涉及名为fifo管道的答案,也可以在此处使用(尽管它可能不是最佳选择)这种特殊情况)。
goldilocks 2013年

@goldilocks:也许我们可以将两个问题合并为一个。我们可以联系主持人吗?
rahmu 2013年

@rahmu:我标记了另一个问题。我想这取决于现在的力量……
goldilocks

Answers:


9

您的代码对我来说似乎是使用tempfiles的完全合理的示例。我会留下:坚持这种方法。真正需要更改的唯一一件事就是创建临时文件的方式。使用类似

 TMP=$(tempfile)

要么

 TMP=$(mktemp)

或者至少

 TMP=/tmp/myscript_$$

这样,您就不会轻易预测名称(安全性)并且避免同时运行脚本的多个实例之间的冲突。


2
从理论上讲,变量分配不需要引号。
glenn jackman 2011年

1
@glenn是的,在这种情况下,它们不应有所不同,因为每个命令通常都会产生一个无空格的字符串。但是,在将命令输出分配给变量的情况下,带引号是一个好习惯-因此,我将坚持使用这种方式。
rozcietrzewiacz 2011年

为了区别起见,删除了最后一个示例中的引号。
rozcietrzewiacz 2011年

3
@roz不,你错过了重点。在shell变量赋值,确认完成任何扩张之前,和现场分裂为变量赋值来完成。因此,var=$(echo lots of spaces); echo "$var"很好,应该lots of spaces作为输出产生。没有人提到的真正警告是命令替换会删除所有尾随的换行符。这不是问题,仅在例如损坏的mktemp文件名后带有换行符的情况下才重要。如果需要,通常的解决方法是var=$(echo command with trailing newline; echo x); var=${var%x}
2012年

1
@ jw013是的,我现在意识到了-一年前写答案时没有意识到。感谢您指出!(正在修复...)
rozcietrzewiacz 2012年

5

您可以使用一个变量:

info="$(getInfo $SERVICE)"
SERV_NAME="$(head -1 $TMPFILE <<<"$info" | sed -e 's/ $//')"
...

来自man ksh

<<<word       A  short  form of here document in which word becomes the
              contents of the here-document after any parameter  expan-
              sion,  command  substitution, and arithmetic substitution
              occur.

优势包括:

  • 启用并行执行。
  • 以我的经验,这比临时文件要快很多。除非您有太多数据最终要交换,否则它应该快几个数量级(仅不包括HD高速缓存缓冲区,对于少量数据量来说可能差不多一样快)。
  • 其他进程或用户无法破坏您的数据。

<<<在我的ksh中似乎不存在。我收到一个错误,但似乎无法在手册页中找到它。我正在使用ksh88。您确定此版本应具有此功能吗?
rahmu 2011年

不; 我想我没有检查正确的man页面(网页上没有提到版本号:/)
l0b0 2011年

<<<bash '这里字符串'。我认为它不会出现在任何其他外壳中。(哦,zsh也许...)
rozcietrzewiacz 2011年

2
@rozcietrzewiacz:的Google man ksh。那里肯定有人提到过。
l0b0 2011年

3
猜猜bash如何实现here-strings和here-docs。sleep 3 <<<"here string" & lsof -p $! | grep 0rsleep 30251 anthony 0r REG 253,0 12 263271 /tmp/sh-thd-7256597168 (deleted)—是的,它使用一个临时文件。
derobert

2

您有两种选择:

  1. getInfo只需检索一次数据(在示例中使用),然后将其存储在文件中。

  2. 您每次都获取数据,而不是将其存储在本地,即getInfo每次都调用

我看不到创建临时文件以避免重新处理/重新获取的问题。

如果您担心将临时文件留在某个地方,可以随时使用trap以确保将其删除,以防脚本被杀死/中断。

trap "rm -f $TMPFILE" EXIT HUP INT QUIT TERM

并用于mktemp为您的临时文件创建唯一的文件名。


1

不用生成文件,而是构造外壳程序分配语句并评估该输出。

for SERVICE in $(myfunc); do
    eval $(getInfo $SERVICE |
               sed -n -e '1/\(.*\) *$/SERV_NAME="\1"/p' \
                   -e '/HOSTNAME/s/^[^=]*=\([^=]*\).*/SERV_HOSTNAME="\1"/p' \
                   -e '/Arguments/^[^:]*:\([^:]*\).*/SERV_ARGS="\1"/p')
    print $SERV_NAME $SEP $SERV_HOSTNAME $SED $SERV_ARGS
done

或者,如果您只想打印信息:

for SERVICE in $(myfunc); do
    getInfo $SERVICE | awk -vsep="$SEP" '
        BEGIN{OFS=sep}
        NR == 1 { sub(/ *$/,""); SERV_NAME=$0 }
        /HOSTNAME/ { split($0, HOST, /=/; SERV_HOSTNAME=HOST[2]; }
        /Arguments/ { split($0, ARGS, /:/; SERV_ARGS }
        END { print SERV_NAME, SERV_HOSTNAME, SERV_ARGS }'
done
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.