4

I want to do a very simple script: just want to find the newest version of a program, say svn, on my computer. I want to load the result into a variable, say mysvn

So I make this script:


#!/bin/sh

mysvn="foobar"
best_ver=0
which -a svn | while read p
do
    version=$("$p" --version | grep 'version ' | grep -oE '[0-9.]+' | head -1)
    if [[ "$version" > "$best_ver" ]]
    then
        best_ver=$version
        mysvn="$p"
    fi
    echo $mysvn
done

echo $mysvn

Very simple in fact ... but it does not work under rxvt (my pseudo-linux terminal), version 2.7.10, running under XP: the final output string is foobar.

Does anybody know why I have this problem?

I have been writing some scripts for the past few months, it is the first time I encounter such a behaviour.

Note: I know how to make it work, with a few changes (just put the main lines into $() )

2
  • try doing temp=$( which -a svn ) and then for p in $temp I think this pipe is your problem but don't have a way to test it right now. Commented Jul 6, 2012 at 14:38
  • 1
    @izomorphius: write this up as a solution. The problem is indeed that the pipe causes the while loop to be executed in a subshell, so any assignments to mysvn are local to that shell. Commented Jul 6, 2012 at 14:53

2 Answers 2

2

The reason this occurs is that the while loop is part of a pipeline, and (at least in bash) any shell commands in a pipeline always get executed in a subshell. When you set mysvn inside the loop, it gets set in the subshell; when the loop ends, the subshell exits and this value is lost. The parent shell's value of mysvn never gets changed. See BashFAQ #024 and this previous question.

The standard solution in bash is to use process substitution rather than a pipeline:

while
...
done < <(which -a svn)

But note that this is a bash-only feature, and you must use #!/bin/bash as your shebang for this to work.

Sign up to request clarification or add additional context in comments.

3 Comments

That's referred to a "process substitution". "Command substitution" looks like $(). Zsh supports process substitution, but it (and ksh93) don't create a subshell when something is piped into while. Ksh93 doesn't seem to support process substitution redirected into done or other builtins. Bash 4.2 has shopt -s lastpipe which prevents a subshell under these circumstances.
Thanks, your answers are both valuable. I see now why my script cannot work properly.
By the way, concerning svn, my script was a little too complicated ... just calling svn --version --quiet is nicer than those awkward greps and if-construct :) So I finally came into this, running with /bin/sh: mysvn=$(which -a svn 2> /dev/null | while read p; do echo $("$p" --version --quiet) "$p"; done | sort -r | head -1 | grep -Eo '".*$')
0

Here on Ubuntu:

:~$ which -a svn | while read p
> do
>   version=$("$p" --version | grep 'version ' | grep -oE '[0-9.]+' | head -1)
>   echo $version
> done
.

so, your version is ., not very nice.

I tried this, and I think it's what you're looking for:

:~$ which -a svn | while read p
> do
>   version=$("$p" --version | grep -oE '[0-9.]+' | head -1)
>   echo $version
> done
1.7.5

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.