Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How to get information out of a subshell?

4 views
Skip to first unread message

Robert Latest

unread,
Dec 9, 2008, 5:32:27 AM12/9/08
to

Hello,

part of a script that I'm writing is supposed to scan a number of
mounted disks for a "tag" file in the root directory and give back a
directory that contains this tag. Here's what I've come up with:

#!/bin/sh
BACKUP_DIR=""
mount | while read dev on dir rest; do
if [ -f "$dir/$BACKUP_TAG" ] ; then BACKUP_DIR="$dir" ; fi
done

echo "Backup Dir is $BACKUP_DIR"

However, since the stuff within the "while" clause is run in a subshell
(the bash manual confirms this), BACKUP_DIR contains nothing after the
loop is run, even if a tag file has been found.

I know I can get what I want using, for example, "sed" and a for loop,
but I just got curious if it also could be done this way.

Thanks,

robert

Janis Papanagnou

unread,
Dec 9, 2008, 5:50:43 AM12/9/08
to

I'd probably fix that by adding one character to the first line...

#!/bin/ksh

(assuming you have AT&T ksh and not some PD clone that hides behind the
name ksh)

>Subject: How to get information out of a subshell?

You could print the information inside the for loop and assign the value
using command substitution...

BACKUP_DIR=$( mount | while ... ; then printf "%s\n" "$dir" ; ... )

(and put the complex command in a function to become better readable)

Janis

>
> Thanks,
>
> robert

Dave B

unread,
Dec 9, 2008, 5:58:31 AM12/9/08
to

This is a typical problem, which happens often. Some possible solutions include:

- using a temporary file.

mount | while read dev on dir rest; do

if [ -f "$dir/$BACKUP_TAG" ] ; then echo "$dir" > tmpfile; fi
done
read BACKUP_DIR < tmpfile


echo "Backup Dir is $BACKUP_DIR"

or, in a different way:

mount > tmpfile


while read dev on dir rest; do
if [ -f "$dir/$BACKUP_TAG" ] ; then BACKUP_DIR="$dir" ; fi

done < tmpfile


echo "Backup Dir is $BACKUP_DIR"


- some shells (bash among them) have process substitution, which lets you
use the output of a command as if it were a file:

while read dev on dir rest; do
if [ -f "$dir/$BACKUP_TAG" ] ; then BACKUP_DIR="$dir" ; fi

done < <(mount)


echo "Backup Dir is $BACKUP_DIR"


- remain in the subshell while you need to access the information:

mount | { while read dev on dir rest; do
if [ -f "$dir/$BACKUP_TAG" ] ; then BACKUP_DIR="$dir" ; fi
done

echo "Backup Dir is $BACKUP_DIR"; }


- use command substitution:

BACKUP_DIR=$(mount | while read dev on dir rest; do
if [ -f "$dir/$BACKUP_TAG" ] ; then echo "$dir" ; fi
done)


echo "Backup Dir is $BACKUP_DIR"


Note that your code might produce more than a single value, in case
BACKUP_TAG is found multiple times. Also, you may want to use the safer form

while IFS= read -r read dev on dir rest

for the read command, although the output of "mount" should be predictable
and not contain unexpected characters.

--
echo 0|sed 's909=mO#3u)o19;s0#0co*)].O0;s()(0bu}=(;s#}#m1$"?0^2{#;
s)")9v2@3%"9$);s[%[o]x(.$e#![;sz(z^+.z;su+ur!z"au;sxzxd?_{g)/x;:b;
s/\(\(.\).\)\(\(..\)*\)\(\(.\).\)\(\(..\)*#.*\6.*\2.*\)/\5\3\1\7/;
tb'|awk '{while((i+=2)<=length($1)-24)a=a substr($1,i,1);print a}'

Stephane Chazelas

unread,
Dec 9, 2008, 6:37:11 AM12/9/08
to
2008-12-9, 10:32(+00), Robert Latest:
[...]

> part of a script that I'm writing is supposed to scan a number of
> mounted disks for a "tag" file in the root directory and give back a
> directory that contains this tag. Here's what I've come up with:
>
> #!/bin/sh
> BACKUP_DIR=""
> mount | while read dev on dir rest; do
> if [ -f "$dir/$BACKUP_TAG" ] ; then BACKUP_DIR="$dir" ; fi
> done
>
> echo "Backup Dir is $BACKUP_DIR"
[...]

Use any IPC mechanism like a pipe or a temp file (you can also
use the non-standard <<EOF <(mount) to combine both). Or write
it the shell way instead of using a loop:

BACKUP_DIR=$(
mount |
awk -v t="$BACKUP_TAG" '{print $3 "/" t}' |
xargs ls -fd 2> /dev/null |
head -1
)

Note that those solutions are not fool proof if some dir names
may contain blanks (or quotes or backslashes for xargs).

--
Stéphane

rcp

unread,
Dec 9, 2008, 1:40:51 PM12/9/08
to

If you use /bin/bash instead of /bin/sh you can use "process substituton".

Here is untested code:

#!/bin/bash
BACKUP_DIR=""


while read dev on dir rest; do
if [ -f "$dir/$BACKUP_TAG" ] ; then BACKUP_DIR="$dir" ; fi

done < <(mount)


echo "Backup Dir is $BACKUP_DIR"

(This assumes BACKUP_TAG is set somehow.)

As you have noted, there is a problem because of the subshell (a separate
process). The BACKUP_DIR in your main process (the one you echo) is not
the same BACKUP_DIR that was set inside the subshell's process.

Process substitution gives us a way to set and echo BACKUP_DIR all
within the same process. The process substitution is the "<(mount)". It
causes mount to run in a separate process. Its output is redirected back
into the main process with the first "<". All the manipulation of
BACKUP_DIR is done in the same process so no surprises.

Bob

Robert Latest

unread,
Dec 9, 2008, 4:35:06 PM12/9/08
to
rcp wrote:

> As you have noted, there is a problem because of the subshell (a separate
> process).

Thanks to everybody for your numerous answers. Actually I'm quite
surprised that I hadn't in fact missed something obvious because I'm
anything but a seasoned shell programmer.

But I'm now using the sed approach:


rootdirs=`mount | sed -r 's/[^ ]+ on ([^ ]+).*/\1/'`

for dir in $rootdirs; do


if [ -f "$dir/$BACKUP_TAG" ] ; then BACKUP_DIR="$dir" ; fi
done

Yeah I know, if more than one disk contains a "tag" then just the first
one gets used; I'll find a way to catch that (basically this is just to
check if any of my backup USB drives is mounted, and even if more than
one were mountd it doesn't really matter).


robert

Chris F.A. Johnson

unread,
Dec 9, 2008, 6:51:11 PM12/9/08
to
On 2008-12-09, Robert Latest wrote:
...


BACKUP_DIR=$( mount | while read dev on dir rest; do
if [ -f "$dir/$BACKUP_TAG" ] ; then printf "%s" "$dir" ; fi
done )


--
Chris F.A. Johnson, author <http://cfaj.freeshell.org/shell/>
Shell Scripting Recipes: A Problem-Solution Approach (2005, Apress)
===== My code in this post, if any, assumes the POSIX locale
===== and is released under the GNU General Public Licence

0 new messages