如何在不派生子shell的情况下获得shell函数的输出?

e0bqpujr  于 2023-01-13  发布在  Shell
关注(0)|答案(7)|浏览(127)

我有以下职能。

hello () {
        echo "Hello"
}
func () {
        hello
        echo "world"
}

如果我不想打印hello函数的输出,但想用它做些什么,我想在某个变量中捕获输出,唯一可能的方法是像下面这样派生一个subshell吗?这不是一个不必要的新子进程的创建吗?这可以优化吗?

func () {
        local Var=$(hello)
        echo "${Var/e/E} world"
}
6rvt4ljy

6rvt4ljy1#

一个丑陋的解决方案是临时替换echo,以便它设置一个全局变量,您可以从函数访问该变量:

func () {
  echo () {
    result="$@"
  }
  result=
  hello
  unset -f echo
  echo "Result is $result"
}

我承认这很讨厌,但避免了副壳。

5jvtdoz2

5jvtdoz22#

使用文件描述符和Bash here字符串怎么样?

hello () {
    exec 3<<<"Hello"
}

func () {
    local Var
    exec 3>&-
    hello && read Var <&3
    echo "${Var/e/E} world"
    exec 3>&-
}

func
oxosxuxt

oxosxuxt3#

你可以让调用者传入一个变量名来保存输出值,然后在函数内部用这个变量名创建一个全局变量,如下所示:

myfunc() { declare -g $1="hello"; }

那么就叫它:

myfunc mystring
echo "$mystring world" # gives "hello world"

因此,您的函数可以重写为:

hello() {
    declare -g $1="Hello"
}

func() {
    hello Var
    echo "${Var/e/E} world"
}

唯一的限制是用于保存输出值的变量不能是局部变量。
相关的帖子,其中谈到使用nameref:

ds97pgxw

ds97pgxw4#

不是一个抨击的答案:至少有一个shell,ksh 优化命令替换$( ... )以 * 不 * 为内置命令产生子shell。当你的脚本倾向于执行很多这样的命令时,这会很有用。

oogrdqng

oogrdqng5#

你有修改hello()函数的选项吗?如果有,那么给予它一个选项,把结果存储在一个变量中:

#!/bin/bash

hello() {
  local text="hello"

  if [ ${#1} -ne 0 ]; then
    eval "${1}='${text}'"
  else
    echo "${text}"
  fi
}

func () {
  local var     # Scope extends to called functions.
  hello var
  echo "${var} world"
}

hello()的一个更紧凑的版本:

hello() {
  local text="hello"
  [ ${#1} -ne 0 ]  && eval "${1}='${text}'" || echo "${text}"
}
vu8f3i0k

vu8f3i0k6#

这并不能从字面上回答这个问题,但对于某些用例来说,这是一种可行的替代方法...
这是@Andrew Vickers的衍生产品,因为你可以依靠eval
与其定义一个函数,不如定义一个我称之为“宏”的东西(C语言的等价物):

MACRO="local \$var=\"\$val world\""

func()
{ 
    local var="result"; local val="hello"; eval $MACRO; 
    echo $result; 
}
sg3maiej

sg3maiej7#

1.将函数的stdout重定向到“自动”管道的写入端FD。然后,在(非分叉)调用之后,...
1.读取同一管道读取端的FD

#!/usr/bin/env bash
# This code prints 'var=2, out=hello' meaning var was set and the stdout got captured
# See: https://stackoverflow.com/questions/7502981/how-to-get-the-output-of-a-shell-function-without-forking-a-sub-shell

main(){
  local -i var=1             # Set value
  local -i pipe_write=0 pipe_read=0  # Just defensive programming
  create_pipe                # Get 2 pipe automatic fd, see function below

  # HERE IS THE CALL
  callee >&"$pipe_write"     # Run function, see below

  exec {pipe_write}>&-       # Close fd of the pipe writter end (to make cat returns)
  local out=$(cat <&"$pipe_read")  # Grab stdout of callee
  exec {pipe_read}>&-        # Just defensive programming
  echo "var=$var, out=$out"  # Show result
}

callee(){
  var=2       # Set an outer scope value
  echo hello  # Print some output
}

create_pipe(){
  : 'From: https://superuser.com/questions/184307/bash-create-anonymous-fifo
    Return: pipe_write and pipe_read fd => to outer scope
  '
  exec 2> /dev/null  # Avoid job control print like [1] 1030612
  tail -f /dev/null | tail -f /dev/null &
  exec 2>&1

  # Save the process ids
  local -i pid2=$!
  local -i pid1=$(jobs -p %+)

  # Hijack the pipe's file descriptors using procfs
  exec {pipe_write}>/proc/"$pid1"/fd/1
  # -- Read
  exec {pipe_read}</proc/"$pid2"/fd/0
  
  disown "$pid2"; kill "$pid1" "$pid2"
}

main

请注意,使用自动正常fd的代码将短得多,如下所示:

exec {fd}<> <(:)

而不是像这段代码那样使用create_pipe函数(复制this answer)。但是这里使用的阅读FD行如下所示:

local out=$(cat <&"$fd")

将阻塞。并且有必要尝试使用如下超时进行阅读:

local out=''
while read -r -t 0.001 -u "${fd}" line; do
  out+="$line"$'\n'
done

但是如果可能的话,我尽量避免任意的sleepstimeouts。\这里,关闭管道的写入端的FD会使读取的cat行在内容的末尾返回(从我贫乏的知识中神奇地得出)。

相关问题