为什么shell不设置管道变量?

时间:2014-01-16 14:13:44

标签: linux shell

我有一个变量,我试图设置这样的东西:

#!/bin/sh

found=0
id=1
echo "Hello" |
while [ $id != 5 ]
do
      id=`expr $id + 1`
      echo $id
      found=1
done

echo "found = $found" // I expect this to be 1

为什么以及如何设置此值? 我被迫使用这样(管道),因为生产环境中的实际代码是:

found=0
id=1
my_mount_name="/opt/insiteone/fuse-mount"
echo "select file_system_id, mount_name from SystemTable" | mysql ifm -uroot -pinsite3 |
while read file_system_id mount_name
do
   if [ "$id" == "$file_system_id" -a "$my_mount_name" == "$mount_name" ]; then
      echo "Match found for file system ID and mount name"
      found=1
   fi
done
echo "found = $found" // I expect this to be 1, when a match, but does not

3 个答案:

答案 0 :(得分:4)

管道在子壳中运行。你可以做一些事情来使它工作,最简单的是:

found=0
id=1
my_mount_name="/opt/insiteone/fuse-mount"
echo "select file_system_id, mount_name from SystemTable" | 
mysql ifm -uroot -pinsite3 | {
while read file_system_id mount_name
do
   if [ "$id" == "$file_system_id" -a "$my_mount_name" == "$mount_name" ]; then
      echo "Match found for file system ID and mount name"
      found=1
   fi
done
echo "found = $found"; }
# Note the enclosing {}. Inside the black, the variable $found is set.
# After this comment, it will be zero.

这种技术可能要求封闭块相当大,因此您可能需要重构脚本的其余部分以使其可用。另一种选择是使用fifo或将echo / mysql管道放入进程替换中。 (后者不是可移植的,但在bash中工作可能就足够了。)但是,在这种特殊情况下,最好做一些类似的事情:

found=0
id=1
my_mount_name="/opt/insiteone/fuse-mount"

echo "select file_system_id, mount_name from SystemTable" |
mysql ifm -uroot -pinsite3 | {
while read file_system_id mount_name
do
   if [ "$id" == "$file_system_id" -a "$my_mount_name" == "$mount_name" ]; then
      echo "Match found for file system ID and mount name"
      exit 0  # Exit the subshell succesfully
   fi
done
exit 1; } && found=1

答案 1 :(得分:3)

您可以将变量传递给子shell的environemnt:

foo=1
bar=2

echo "sds" | foo="$foo" bar="$bar" while ... 

答案 2 :(得分:2)

正如@fedorqui评论的那样,bash将管道组件放在不同的子shell中,因此当子shell退出时,对变量的更改会消失。

有两种策略可以解决这个问题:

  1. 仅在同一子shell中使用已更改的变量

    echo "Hello" |
    {
        while [ $id != 5 ]
        do
            ((id++))
            echo $id
            found=1
        done
        echo "found = $found" // I expect this to be 1
    }
    

    如果你有很多依赖这些变量的代码

  2. ,这可能是个问题
  3. 用流程替换替换管道。这意味着while循环不在子shell中执行,它在当前shell中运行:

    while [ $id != 5 ]
    do
        ((id++))
        echo $id
        found=1
    done < <(echo "Hello")
    echo "found = $found" // I expect this to be 1
    

    这可能会导致可读性差,但您可以根据需要在<(...)内放置尽可能多的换行符。

    您的生产代码使用流程替换(和bash / ksh条件语法)重写:

    found=0
    id=1
    my_mount_name="/opt/insiteone/fuse-mount"
    
    while read file_system_id mount_name; do
        if [[ $id == $file_system_id ]] && [[ $my_mount_name == $mount_name ]]; then
            echo "Match found for file system ID and mount name"
            found=1
        fi
    done < <(
        echo "select file_system_id, mount_name from SystemTable" | 
        mysql ifm -uroot -pinsite3 
    )
    echo "found = $found"