【问题标题】:/dev/stdin with herestring/dev/stdin 带有此处字符串
【发布时间】:2023-12-07 20:25:01
【问题描述】:

我想要一个可以从文件或标准输入中获取输入的 Bash 脚本,例如 grep

$ cat hw.txt
Hello world

$ grep wor hw.txt
Hello world

$ echo 'Hello world' | grep wor
Hello world

$ grep wor <<< 'Hello world'
Hello world

一切都很好。但是使用以下脚本

read b < "${1-/dev/stdin}"
echo $b

如果使用herestring会失败

$ hw.sh hw.txt
Hello world

$ echo 'Hello world' | hw.sh
Hello world

$ hw.sh <<< 'Hello world'
/opt/a/hw.sh: line 1: /dev/stdin: No such file or directory

【问题讨论】:

  • 在你的情况下,顺便说一句,写if [[ $# = 0 ]] ; then read b ; else read b &lt; "$1" ; fi 很容易解决这个问题。但我不知道为什么需要这样的解决方法。

标签: bash cygwin stdin heredoc


【解决方案1】:

以这种方式使用/dev/stdin 可能会出现问题,因为您试图使用文件系统中的名称(/dev/stdin)而不是使用 bash 已经将文件描述符作为标准输入(文件描述符 0)。

这里有一个小脚本供你测试:

#!/bin/bash

echo "INFO: Listing of /dev"
ls -al /dev/stdin

echo "INFO: Listing of /proc/self/fd"
ls -al /proc/self/fd

echo "INFO: Contents of /tmp/sh-thd*"
cat /tmp/sh-thd*

read b < "${1-/dev/stdin}"
echo "b: $b"

在我的 cygwin 安装中,这会产生以下结果:

./s <<< 'Hello world'


$ ./s <<< 'Hello world'
INFO: Listing of /dev
lrwxrwxrwx 1 austin None 15 Jan 23  2012 /dev/stdin -> /proc/self/fd/0
INFO: Listing of /proc/self/fd
total 0
dr-xr-xr-x 2 austin None 0 Mar 11 14:27 .
dr-xr-xr-x 3 austin None 0 Mar 11 14:27 ..
lrwxrwxrwx 1 austin None 0 Mar 11 14:27 0 -> /tmp/sh-thd-1362969584
lrwxrwxrwx 1 austin None 0 Mar 11 14:27 1 -> /dev/tty0
lrwxrwxrwx 1 austin None 0 Mar 11 14:27 2 -> /dev/tty0
lrwxrwxrwx 1 austin None 0 Mar 11 14:27 3 -> /proc/5736/fd
INFO: Contents of /tmp/sh-thd*
cat: /tmp/sh-thd*: No such file or directory
./s: line 12: /dev/stdin: No such file or directory
b: 

此输出显示 bash 正在创建一个临时文件来保存您的 HERE 文档 (/tmp/sh-thd-1362969584) 并使其在文件描述符 0 标准输入上可用。但是,临时文件已从文件系统中取消链接,因此无法通过文件系统名称(例如 /dev/stdin)通过引用进行访问。您可以通过读取文件描述符 0 来获取内容,但不能通过尝试打开 /dev/stdin

在 Linux 上,上面的 ./s 脚本给出以下内容,表明文件已被取消链接:

INFO: Listing of /dev
lrwxrwxrwx 1 root root 15 Mar 11 09:26 /dev/stdin -> /proc/self/fd/0
INFO: Listing of /proc/self/fd
total 0
dr-x------ 2 austin austin  0 Mar 11 14:30 .
dr-xr-xr-x 7 austin austin  0 Mar 11 14:30 ..
lr-x------ 1 austin austin 64 Mar 11 14:30 0 -> /tmp/sh-thd-1362965400 (deleted) <---- /dev/stdin not found
lrwx------ 1 austin austin 64 Mar 11 14:30 1 -> /dev/pts/12
lrwx------ 1 austin austin 64 Mar 11 14:30 2 -> /dev/pts/12
lr-x------ 1 austin austin 64 Mar 11 14:30 3 -> /proc/10659/fd
INFO: Contents of /tmp/sh-thd*
cat: /tmp/sh-thd*: No such file or directory
b: Hello world

更改您的脚本以使用提供的标准输入,而不是尝试通过 /dev/stdin 进行引用。

if [ -n "$1" ]; then
    read b < "$1"
else
    read b
fi

【讨论】:

  • 在这种特定情况下实际上并非如此。如果在重定向或测试表达式中使用,Bash 会在内部处理任何 /dev/fd/*(这就是它们在手册中列出的原因)。如果您使用的是 Bash 或 ksh93,只要它们不只是作为内置或外部命令的参数提供,它们就可以便携使用。您甚至可以使用/dev/stdin 写入 Bash 字符串。然而,并不是所有的 shell 都为 heredocs 使用临时文件。 Dash/busybox 使用管道。
  • @ormaaj 很有趣。手册指出 /dev/stdin 是经过特殊处理的,但我对 bash 源代码的初步阅读表明,如果 /dev/stdin 在配置阶段可用,那么 /dev/stdin 将被视为任何其他文件。即在源代码中,HAVE_DEV_STDIN 将被定义,因此不会出现在特殊文件名列表中。
  • 这可能是,但它也不应该真的很重要。这在 Linux 上运行良好:dash -c 'x=$(mktemp); echo test &gt;"$x"; { unlink -- "$x"; cat; cat /proc/self/fd/0; } &lt;"$x"'
  • @ormaaj 虽然这在 Linux 上有效,但在 Cygwin 下却失败了。如果您在dash 命令上运行strace,您可以看到/proc/self/fd/0 已打开并分配了它自己的文件描述符,而不是dup 调用。我不完全理解,但在 Linux 上,似乎允许打开 /proc/self/fd/0 -&gt; foo (deleted file) 并返回文件的内容。
  • 在 Linux 上,echo test &gt; /tmp/foo; { rm /tmp/foo; echo A; cat /tmp/foo; echo B; cat /proc/self/fd/0; } &lt; /tmp/foo 给出的输出 A cat: /tmp/foo: No such file or directory B test 表明 /proc/self/fd/0 可以打开并生成原始文本。在 cygwin 上,输出为 A cat: /tmp/foo: No such file or directory B cat: /proc/self/fd/0: No such file or directory 。由于 /proc/ 仅在 Cygwin 上模拟,而不是内核的一部分,这可能是造成差异的原因。
【解决方案2】:

bash 专门解析一些文件名(如/dev/stdin),以便即使它们实际上不存在于文件系统中也能被识别。如果您的脚本顶部没有#!/bin/bash,并且/dev/stdin 不在您的文件系统中,则您的脚本可能会使用/bin/sh 运行,这会期望/dev/stdin 实际上是一个文件。

(这可能不是答案,而是对Austin's answer 的评论。)

【讨论】:

  • 查看我对 ormaaj 的评论。在我看来,/dev/stdin 只有在编译时不存在时才会被特殊处理。有兴趣让其他人阅读代码并进行验证。
  • 有趣!我只是按照手册页所说的内容进行了介绍,特别是从未真正查看过有和没有真正文件系统条目的机器上的用法。
【解决方案3】:
$ cat ts.sh 
read b < "${1-/dev/stdin}"
echo $b

$ ./ts.sh <<< 'hello world'
hello world

对我来说没问题。我在 Mac OS X 上使用 bash 4.2.42。

【讨论】:

    【解决方案4】:

    这里有错别字

    read b < "${1-/dev/stdin}"
    

    试试

    read b < "${1:-/dev/stdin}"
    

    【讨论】:

    • 两种符号都可以接受;外壳处理两者。没有冒号的版本是否按预期工作更值得怀疑。
    • ${1-/dev/stdin} 仅在未设置 $1 时将 $1 替换为 "/dev/stdin"。 ${1:-/dev/stdin} 也将替换 $1 如果它设置为空字符串。
    • 有趣,在 bash 手册页中找不到任何对 ${parameter-word} 扩展的引用。但它就像 chepner 所说的那样工作......我的错。
    • 文档含糊不清;手册页中有一个句子,就在各种运算符列表之前,描述了从它们中省略冒号的效果。很难搜索,如果只是略读很容易错过。
    最近更新 更多