将第一个命令的输出作为输入传递给第二个命令,以便第一个命令在当前shell中运行

时间:2016-07-18 06:51:26

标签: shell posix

以下代码描述了我的问题。

# File: foo.sh

# We have a work function that may define variables and produce some
# complex output.
work()
{
    a=foo
    ps -ef
}

# This function uses the work function.
f()
{
    # We want to filter the complex output produced by work function but
    # at the same time we want that any variables defined by work
    # function is available in this function.
    work | head -n 2

    # Since pipeline launches subshells, the variable defined by work
    # function is no longer available in this function.
    echo a: $a
}

f

这是输出。

$ sh foo.sh
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Jul15 ?        00:00:03 /sbin/init
a:

由于管道创建了一个子shell,这意味着当我们返回work()时,f()中定义的变量会丢失,我以这种方式修复了问题。

# File: bar.sh 

# We have a work function that may define variables and produce some
# complex output.
work()
{
    a=foo
    ps -ef
}

# This function uses the work function.
f()
{
    # We want to filter the complex output produced by work function but
    # at the same time we want that any variables defined by work
    # function is available in this function.
    work > out.txt
    head -n 2 out.txt
    echo a: $a
}

f

这是输出。

$ sh bar.sh 
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Jul15 ?        00:00:03 /sbin/init
a: foo

虽然第二个脚本适用于这个玩具示例,但是当f()最终在递归中调用自身会导致out.txt被覆盖时,它会导致问题(可解决的问题)当caller-f()仍然依赖它时,调用-f()。当然,可以通过充分注意out.txt的使用来解决它,即仅在f()调用自身之前使用out.txt并避免在调用之下使用out.txt

这让我想知道是否有任何方法可以在不依赖于创建临时文件的情况下解决此问题。有没有办法将work的输出传递给另一个命令而不必求助于文件I / O,以便work在当前shell中执行?我正在寻找可以在任何POSIX shell上运行的解决方案。

3 个答案:

答案 0 :(得分:2)

kludge(在yash中测试),将变量输出到文件描述符3 并绕过head,然后将其复制回 STDIN

{ { work ; echo a: $a 1>&3 ; } | head -n 2 ; } 3>&1

输出:

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Jul15 ?        00:00:41 /sbin/init splash
a: foo

注意大括号的两个级别:

  1. 在结束之前,将文件描述符3 移回 STDIN 而不head看到它。
  2. workecho保留在同一个shell中。
  3. 与函数f中的上述代码相同:

    f() { { { work ; echo a: $a 1>&3 ; } | head -n 2 ; } 3>&1 }
    

答案 1 :(得分:1)

我已经实现了一个函数public function userDocuments() { return $this->hasMany('App\Models\Document'); } ,用于以请求的方式执行管道(即管道的第一个命令在当前shell中执行)。但请注意,实现会创建临时文件,因此不符合避免文件I / O的要求。

<强>用法:

以下任何形式和其他变体都有效:

pseudopipe

当前实施的限制:

  • 只有正确处理由两个命令组成的管道

  • 这些命令不得包含带引号或转义的&#39; pseudopipe "cmd1|cmd2" pseudopipe 'cmd1|cmd2' pseudopipe cmd1\|cmd2 &#39;字符。例如,以下内容无法按预期工作:

    |
  • 您的脚本不得使用保留变量 pseudopipe "work|grep '|'" PSEUDOPIPE_TMPDIR PSEUDOPIPE_CMD1 < /强>

实施和示例:

PSEUDOPIPE_CMD2

<强>输出:

#!/bin/sh

pseudopipe()
{
    PSEUDOPIPE_TMPDIR=$(mktemp -d)
    PSEUDOPIPE_CMD1=$(echo "$@"|sed 's/|/\n/g'|head -n 1)
    PSEUDOPIPE_CMD2=$(echo "$@"|sed 's/|/\n/g'|tail -n 1)
    mkfifo "$PSEUDOPIPE_TMPDIR"/fifo
    eval "$PSEUDOPIPE_CMD2 < $PSEUDOPIPE_TMPDIR/fifo &" \
         "$PSEUDOPIPE_CMD1 > $PSEUDOPIPE_TMPDIR/fifo ;" \
         "rm -r $PSEUDOPIPE_TMPDIR"
    wait $!
}

work()
{
    a=foo
    ps -ef
}

f()
{
    a=bar
    echo "before pseudopipe: a: $a"
    pseudopipe "work|head -n 2"
    echo "after pseudopipe:  a: $a"
}

f

答案 2 :(得分:1)

问题是您需要来自子shell的多条信息。一种解决方案是将您想要的所有内容从子shell放入标准输出管道并将其全部处理回父shell端:

work()
{
    a=foo
    echo $a
    ps -ef
}

然后你要在父shell端做一些解析工作。在这里,我将分隔符设置为换行符,并将返回字符串转换为每个数组索引处有一行的数组:

f()
{
    output=$(work | head -n 3)
    IFS=$'\n'
    lines=($output)
    a=${lines[0]}
    echo ${lines[1]}
    echo ${lines[2]}
    echo a=$a
}

f

另一个解决方案是使用命名管道并将您感兴趣的每个变量分配到自己的命名管道中。在子shell端,您可以创建和编写管道:

mkfifo /tmp/variable_a
echo $a > /tmp/variable_a

在父shell端,您从同一个命名管道中读取:

read a < /tmp/variable_a
rm -f /tmp/variable_a