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 $() )

Bentoy13
  • 4,886
  • 1
  • 20
  • 33
  • 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. – Ivaylo Strandjev Jul 06 '12 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. – chepner Jul 06 '12 at 14:53

2 Answers2

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.

Community
  • 1
  • 1
Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
  • 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. – Dennis Williamson Jul 06 '12 at 16:05
  • Thanks, your answers are both valuable. I see now why my script cannot work properly. – Bentoy13 Jul 09 '12 at 06:54
  • 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 '".*$')` – Bentoy13 Jul 09 '12 at 08:32
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
Miguel
  • 566
  • 1
  • 5
  • 16