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
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
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}'
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
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
> 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
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