如何将字符串传递给需要文件的命令?

Flu*_*lux 53 bash shell-script

假设一个程序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.
Run Code Online (Sandbox Code Playgroud)

当需要文件名参数时,如何将配方传递给命令?

我想创建一个临时文件只是为了传递文件,但我想知道是否有其他方法可以解决这个问题。

wur*_*tel 55

您可以使用/dev/stdin代表标准输入的“假”文件名。

所以执行这个:

echo "$the_recipe" | cook /dev/stdin
Run Code Online (Sandbox Code Playgroud)

echo 命令和管道将指定变量的内容发送到下一个命令cook的标准输入,然后打开标准输入(作为单独的文件描述符)并读取它。

  • @Flux 您可以使用 `/dev/fd` 来完成,但这超出了您最初问题的范围。但我认为你会在 https://unix.stackexchange.com/questions/tagged/file-descriptors 中找到这样的例子 (5认同)
  • 这种方法是否仅限于只接受一个参数的命令?如果`cook` 接受两个文件参数,并且我有`the_recipe1` 和`the_recipe2`(两者都是字符串变量),那么这个方法是否仍然有效? (2认同)

Ini*_*ian 45

使用bashshell的进程替换功能的另一种方法是在/tmp或下创建 FIFO /var/tmp,或者使用命名文件描述符 ( /dev/fd/*),具体取决于操作系统。替换语法( <(cmd)) 被FIFO 或FD 的名称替换,并且其中的命令在后台运行。

cook <(printf '%s\n' "${the_recipe}")
Run Code Online (Sandbox Code Playgroud)

该命令cook现在读取变量的内容,就像它在文件中可用一样。

这也可以用于多个输入文件:

cook <(printf '%s\n' "${the_1st_recipe}") <(printf '%s\n' "${the_2nd_recipe}")
Run Code Online (Sandbox Code Playgroud)

  • 请注意,进程替换是 ksh 功能(现在 zsh 和 bash 也支持)。`cook` 可能需要一个文本文件,所以你应该把它变成 `'%s\n'` 而不是 `'%s'`。另见`zsh`中的`&lt;(&lt;&lt;&lt;$the_recipe)`。 (5认同)
  • 或者,在进程替换中使用 `cat &lt;&lt;&lt;"$the_recipe"`。 (4认同)

Gil*_*il' 24

这取决于应用程序的功能。它可能会也可能不会坚持一个命名文件,它可能会也可能不会坚持一个可查找的文件。

匿名管道

一些应用程序只是从任何地方读取输入。通常,它们默认从标准输入读取。如果您有字符串形式的数据,并且希望将其传递给应用程序的标准输入,则有几种方法。您可以通过管道传递信息:

printf '%s' "$the_recipe" | cook     # passes $the_recipe exactly
printf '%s\n' "$the_recipe" | cook   # passes $the_recipe plus a final newline
Run Code Online (Sandbox Code Playgroud)

不要echo在这里使用,因为根据 shell,它可能会破坏反斜杠。

如果应用程序坚持使用文件参数,它可能接受-表示标准输入。这是一个普遍的惯例,但并不系统。

printf '%s\n' "$the_recipe" | cook -
Run Code Online (Sandbox Code Playgroud)

如果应用程序需要实际的文件名,则传递/dev/stdin到表示标准输入。

printf '%s\n' "$the_recipe" | cook /dev/stdin
Run Code Online (Sandbox Code Playgroud)

对于某些应用程序,即使这还不够:它们需要具有特定属性的文件名,例如具有给定扩展名,或者它们需要在创建临时文件的可写目录中的文件名。在这种情况下,您通常需要一个临时文件,尽管有时命名管道就足够了。

命名管道

可以为管道命名。我提到这一点是为了完整性,但这很少是最方便的方式。它确实避免了在磁盘上获取数据。如果应用程序需要一个对名称有限制的文件,但不需要该文件是可查找的,则它是合适的。

mkfifo named.pipe
printf '%s\n' "$the_recipe" >named.pipe & writer=$!
cook named.pipe
rm named.pipe
wait "$writer"
Run Code Online (Sandbox Code Playgroud)

(省略错误检查。)

临时文件

如果应用程序需要一个可搜索的文件,你需要创建一个临时文件。可查找文件是应用程序可以来回访问、一次读取任意部分的文件。管道不允许这样做:它需要从头到尾依次读取,不能倒退。

Bash、ksh 和 zsh 有一种方便的语法,可以通过临时文件将字符串作为输入传递。请注意,它们总是在字符串后附加一个换行符(即使已经有一个换行符)。

cook <<<"$the_recipe"
Run Code Online (Sandbox Code Playgroud)

使用其他shell,或者如果要控制哪个目录包含临时文件,则需要手动创建临时文件。大多数 unices 提供了一个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"
Run Code Online (Sandbox Code Playgroud)

要选择创建临时文件的位置,请TMPDIRmktemp调用设置环境变量。例如,要在当前目录而不是临时文件的默认位置创建临时文件:

tmp=$(TMPDIR="$PWD" mktemp)
Run Code Online (Sandbox Code Playgroud)

(使用$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"
Run Code Online (Sandbox Code Playgroud)


rac*_*man 5

在尝试更复杂的解决方案之前,请务必检查您的版本是否cook支持参数-(破折号)作为文件名。如果支持此约定,它会告诉您cook从标准输入中读取,如下所示:

echo "$the_recipe" | cook -
Run Code Online (Sandbox Code Playgroud)

或者

cook - <<<"$the_recipe"
Run Code Online (Sandbox Code Playgroud)