假设一个程序cook
采用一个参数:包含要烹饪的食物食谱的文本文件的路径名。假设我希望从bash
脚本中调用该程序,还假设我已经在字符串变量中包含了配方:
#!/bin/bash
the_recipe="$(cat << EOF
wash cucumbers
wash knife
slice cucumbers
EOF
)"
cook ... # What should I do here? It expects a file, but I only have a string.
当命令需要文件名参数时,如何将配方传递给命令?
我考虑过创建一个临时文件只是为了传递文件,但我想知道是否有其他方法可以解决这个问题。
答案1
/dev/stdin
您可以使用代表标准输入的“假”文件名。
所以执行这个:
echo "$the_recipe" | cook /dev/stdin
echo 命令和管道将指定变量的内容发送到下一个命令的标准输入cook
,然后打开标准输入(作为单独的文件描述符)并读取它。
答案2
使用 shell 的进程替换功能的另一种方法是在或bash
下创建 FIFO ,或使用命名文件描述符 ( ),具体取决于操作系统。替换语法()被 FIFO 或 FD 的名称替换,并且其中的命令在后台运行。/tmp
/var/tmp
/dev/fd/*
<(cmd)
cook <(printf '%s\n' "${the_recipe}")
该命令cook
现在读取变量的内容,就像它在文件中一样。
这也可以用于多个输入文件:
cook <(printf '%s\n' "${the_1st_recipe}") <(printf '%s\n' "${the_2nd_recipe}")
答案3
这取决于应用程序的功能。它可能会或可能不会坚持使用命名文件,也可能会或可能不会坚持使用可查找文件。
匿名管道
有些应用程序只是从任何地方读取输入。通常它们默认从标准输入读取。如果您有字符串形式的数据,并且希望将其传递到应用程序的标准输入,有几种方法。您可以通过管道传递信息:
printf '%s' "$the_recipe" | cook # passes $the_recipe exactly
printf '%s\n' "$the_recipe" | cook # passes $the_recipe plus a final newline
不要echo
在此处使用,因为根据 shell 的不同,它可能会破坏反斜杠。
如果应用程序坚持使用文件参数,它可能会接受-
标准输入。这是一个常见的约定,但并不系统。
printf '%s\n' "$the_recipe" | cook -
如果应用程序需要实际的文件名,则传递/dev/stdin
以表示标准输入。
printf '%s\n' "$the_recipe" | cook /dev/stdin
对于某些应用程序来说,这还不够:它们需要具有某些属性的文件名,例如具有给定的扩展名,或者它们需要在创建临时文件的可写目录中的文件名。在这种情况下,您通常需要一个临时文件,尽管有时命名管道就足够了。
命名管道
可以给管道命名。我提到这一点是为了完整性,但这很少是最方便的方法。它确实避免了在磁盘上获取数据。如果应用程序需要一个名称有限制的文件,但不需要该文件可查找,那么它是合适的。
mkfifo named.pipe
printf '%s\n' "$the_recipe" >named.pipe & writer=$!
cook named.pipe
rm named.pipe
wait "$writer"
(省略错误检查。)
临时文件
如果应用程序需要一个可寻找的文件,您需要创建一个临时文件。可查找文件是应用程序可以来回移动、一次读取任意部分的文件。管道不允许这样做:它需要从头到尾按顺序读取,不能倒退。
Bash、ksh 和 zsh 具有方便的语法,可通过临时文件将字符串作为输入传递。请注意,它们总是在字符串中附加一个换行符(即使已经有一个换行符)。
cook <<<"$the_recipe"
使用其他 shell,或者如果您想控制哪个目录包含临时文件,则需要手动创建临时文件。大多数 unice 都提供mktemp
公用事业安全地创建临时文件。 (永远不要使用类似的东西!它允许其他程序,甚至以其他用户身份运行,劫持该文件。)这是一个脚本,它创建一个临时文件,并在中断时负责将其删除。… >/tmp/temp.$$
tmp=
trap 'rm -f "$tmp"' EXIT
trap 'rm -f "$tmp"; trap "" HUP; kill -INT $$' HUP
trap 'rm -f "$tmp"; trap "" INT; kill -INT $$' INT
trap 'rm -f "$tmp"; trap "" TERM; kill -INT $$' TERM
tmp=$(mktemp)
printf '%s\n' "$the_recipe" >"$tmp"
cook "$tmp"
要选择创建临时文件的位置,请设置TMPDIR
调用的环境变量mktemp
。例如,要在当前目录而不是临时文件的默认位置创建临时文件:
tmp=$(TMPDIR="$PWD" mktemp)
(使用$PWD
而不是.
为您提供绝对文件名,这意味着您可以安全地调用cd
脚本,而不必担心$tmp
不再指定相同的文件。)
如果应用程序需要具有特定扩展名的文件名,则需要创建一个临时目录并在其中创建文件。要创建临时目录,请使用mktemp -d
.TMPDIR
如果您想控制临时目录的创建位置,请再次设置环境变量。
tmp=
trap 'rm -rf "$tmp"' EXIT
trap 'rm -rf "$tmp"; trap "" HUP; kill -INT $$' HUP
trap 'rm -rf "$tmp"; trap "" INT; kill -INT $$' INT
trap 'rm -rf "$tmp"; trap "" TERM; kill -INT $$' TERM
tmp=$(mktemp -d)
printf '%s\n' "$the_recipe" >"$tmp/myfile.ext"
cook "$tmp/myfile.ext"
答案4
在尝试更复杂的解决方案之前,请务必检查您的版本是否cook
支持参数-
(破折号)作为文件名。如果支持此约定,它会告诉cook
您从标准输入中读取,如下所示:
echo "$the_recipe" | cook -
或者
cook - <<<"$the_recipe"