Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/security/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Function Bourne shell函数返回变量始终为空_Function_Shell_Return Value_Sh - Fatal编程技术网

Function Bourne shell函数返回变量始终为空

Function Bourne shell函数返回变量始终为空,function,shell,return-value,sh,Function,Shell,Return Value,Sh,下面的Bourne shell脚本,给定一条路径,应该测试路径的每个组件是否存在;然后设置一个仅包含实际存在的组件的变量 #! /bin/sh set -x # for debugging test_path() { path="" echo $1 | tr ':' '\012' | while read component do if [ -d "$component" ] then if [ -z "$path" ]

下面的Bourne shell脚本,给定一条路径,应该测试路径的每个组件是否存在;然后设置一个仅包含实际存在的组件的变量

#! /bin/sh
set -x             # for debugging

test_path() {
  path=""
  echo $1 | tr ':' '\012' | while read component
  do
    if [ -d "$component" ]
    then
      if [ -z "$path" ]
      then path="$component"
      else path="$path:$component"
      fi
    fi
  done
  echo "$path"    # this prints nothing
}

paths=/usr/share/man:\
/usr/X11R6/man:\
/usr/local/man

MANPATH=`test_path $paths`
echo $MANPATH
运行时,它始终不打印任何内容。使用set-x的跟踪是:


为什么最终echo$路径为空?while循环中的$path变量在每次迭代中都以增量方式设置得很好。

管道运行子shell中涉及的所有命令,包括整个while。。。环因此,对该循环中变量的所有更改仅限于子shell,父shell脚本不可见

解决这一问题的一种方法是在。。。循环并将echo放入一个完全在子shell中执行的列表中,以便修改后的变量$path对echo可见:

但是,我建议使用如下方式:

test_path()
{
    echo "$1" | tr ':' '\n' |
    while read dir
    do
        [ -d "$dir" ] && printf "%s:" "$dir"
    done |
    sed 's/:$/\n/'
}
。。。但这是品味的问题

编辑:正如其他人所说,您观察到的行为取决于外壳。将流水线命令描述为在子shell中运行,但这不是要求:

此外,多命令管道的每个命令都位于子shell环境中;但是,作为扩展,管道中的任何或所有命令都可以在当前环境中执行


Bash在子shell中运行这些命令,但有些shell在主脚本上下文中运行最后一个命令,而管道中只有前面的命令在子shell中运行。

这应该适用于理解函数的Bourne shell,也适用于Bash和其他shell:

test_path() {
  echo $1 | tr ':' '\012' |
  {
  path=""
  while read component
  do
    if [ -d "$component" ]
    then
      if [ -z "$path" ]
      then path="$component"
      else path="$path:$component"
      fi
    fi
  done
  echo "$path"    # this prints nothing
  }
}
大括号的内部集合将命令分组为一个单元,因此路径仅在子shell中设置,但从同一个子shell中回显

为什么最终echo$路径为空

直到最近,Bash还将为管道的所有组件提供它们自己的进程,与运行管道的shell进程分开。 独立进程==独立的地址空间,不共享变量

在ksh93和最近的Bash中,可能需要shopt设置,shell将运行调用shell中管道的最后一个组件,因此当循环退出时,将保留在循环内更改的任何变量

另一种实现方法是使用括号确保echo$路径与循环处于相同的过程中:

#! /bin/sh
set -x             # for debugging

test_path() {
  path=""
  echo $1 | tr ':' '\012' | ( while read component
  do
    [ -d "$component" ] || continue

    path="${path:+$path:}$component"
  done
  echo "$path"
  )
}

注意:我简化了内部if。没有其他方法,因此可以用快捷方式替换测试。另外,可以使用S{var:+…}参数替换技巧将两个路径分配组合为一个。

您的脚本在Solaris 11下运行得很好,没有任何更改,而且可能在大多数商业Unix(如AIX和HP-UX)下也是如此,因为在这些操作系统下,/bin/sh的底层实现是由ksh提供的。如果zsh支持/bin/sh,也会出现这种情况

它可能对您不起作用,因为您的/bin/sh是由bash、dash、mksh或busybox sh中的一个实现的,它们都在子shell中处理管道的每个组件,而ksh和zsh都将管道的最后一个元素保留在当前shell中,从而节省了不必要的fork

当bash提供sh时,可以通过在管道之前的某个位置添加以下行来修复脚本,使其正常工作:

shopt -s lastpipe
或者更好,如果您不想保持便携性:

command -v shopt > /dev/null && shopt -s lastpipe
这将使脚本能够为ksh和zsh工作,但仍然不能为dash、mksh或原始的bourneshell工作


注意,POSIX标准允许bash和ksh行为

有什么方法可以得到我想要的结果吗?这很有趣,我不知道bash中的lastpipe选项。谢谢你的指点。这可能允许在bash脚本中进行一些简化。/bin/sh通常只表示与POSIX兼容的shell,它不一定是,甚至通常不是实际的Bourne shell。
shopt -s lastpipe
command -v shopt > /dev/null && shopt -s lastpipe