LinuxQuestions.org
Welcome to the most active Linux Forum on the web.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 07-03-2011, 03:56 AM   #1
frenchn00b
Senior Member
 
Registered: Jun 2007
Location: E.U., Mountains :-)
Distribution: Debian, Etch, the greatest
Posts: 2,561

Rep: Reputation: 57
SH: Passing a variable out of a "while ; do ; done" loop


Hello SH fan Coders,

"While ; do ; done" is very convenient for SH coding. However sometimes you may be annoyed by your computed variable within the "while do done" type loop.
What to do how to pass it out of the loop to the outside of the bash code? A solution is to write it into the /tmp or on the disk... and to call it back after.

- not elegant... really not...

Anyone would know a trick another alternative that would look nicer?

Thanks in advance ...


Code:
			# Count file total size
			TOTAL_SIZE=0
			LISTOFFILES=`cat "$HOME/.fvwmoscfg/fvwmburnerlist.lst"`
			echo "$LISTOFFILES"  | while read i ; do 
				SIZE=`du -bs $i | cut -f 1`
				TOTAL_SIZE=`expr $SIZE + $TOTAL_SIZE`
				echo "$TOTAL_SIZE" > "$HOME/.fvwmoscfg/fvwmburnerlisttotalsize.lst"
			done
			TOTAL_SIZE=`cat $HOME/.fvwmoscfg/fvwmburnerlisttotalsize.lst`
			echo "The total size of all files and folders is : $TOTAL_SIZE"
 
Old 07-03-2011, 05:56 AM   #2
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,007

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
Your issue is down to the fact that you feed your loop through a subshell (ie you pipe the data in). Try feeding the values / files directly into the loop with
redirection:
Code:
while read i
do
    <your stuff here>
done<"$HOME/.fvwmoscfg/fvwmburnerlist.lst"
You should now find all variables from within the loop are available outside
 
Old 07-23-2011, 03:30 PM   #3
frenchn00b
Senior Member
 
Registered: Jun 2007
Location: E.U., Mountains :-)
Distribution: Debian, Etch, the greatest
Posts: 2,561

Original Poster
Rep: Reputation: 57
Thank you it works !!


Try this script and see if it gives you any clues:
Code:
echo "---" >tmp
while read line; do
ps aux | grep "$0" | grep -v grep
done <tmp
rm -f tmp
 
Old 07-23-2011, 04:00 PM   #4
MTK358
LQ 5k Club
 
Registered: Sep 2009
Posts: 6,443
Blog Entries: 3

Rep: Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723
Quote:
Originally Posted by frenchn00b View Post
Try this script and see if it gives you any clues:
Code:
echo "---" >tmp
while read line; do
ps aux | grep "$0" | grep -v grep
done <tmp
rm -f tmp
???
 
Old 07-23-2011, 04:16 PM   #5
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,007

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
Quote:
Try this script and see if it gives you any clues
I am with MTK ... why would it give me any clues ... you asked the question? Also, the script you have presented makes little sense.
 
Old 07-23-2011, 04:56 PM   #6
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948
I am assuming your SH in the thread title means you are using a POSIX shell, and don't want to use e.g. Bash features.

The solution is obvious when the subshell does not need to output anything to standard output - for example,
Code:
#!/bin/sh

TOTAL=$(
	SIZE=0
	for FILE in "$@" ; do
		SIZE="$(du -bs "$FILE" | cut -f 1) + $SIZE"
	done
	expr $SIZE
)
echo "Total size: $TOTAL bytes."
I assume frenchn00b and everybody else is quite aware of that.

When the subshell needs to write to standard output, use a sub-subshell, and redirect its standard output to an extra descriptor. Use yet another descriptor to output to the original standard output.

In other words, swap standard output with an extra descriptor for a sub-subshell, then swap them back in the subshell. That way the sub-subshell can write to standard output and standard error normally, but if it wants to write to the original output, it has to use the extra descriptor.

Because we cannot swap descriptors in POSIX shells, we need two temporary extra descriptors. The easiest is to open the two extra descriptors in the outer shell, but you can of course reuse them any number of times. (Alternatively, you can use another subshell.)

Code:
#!/bin/sh

# Use 9 as the result descriptor,
# and 8 as the temporary output descriptor.
exec 8>&1 9>&1

TOTAL=$( (
		SIZE="0"
		for FILE in "$@" ; do
			TEMP=$(du -bs "$FILE" | cut -f 1)
			echo "$FILE: $TEMP bytes"
			SIZE="$TEMP + $SIZE"
		done
		expr $SIZE >&9

	) 9>&1 >&8 )

# The descriptors 8 and 9 can now be reused.

echo "Total: $TOTAL bytes"
You can of course use a pipe, not just a simple subshell:
Code:
#!/bin/sh

# Use 9 as the result descriptor,
# and 8 as the temporary output descriptor.
exec 8>&1 9>&1

TOTAL=$(
	for FILE in "$@" ; do
		echo "$FILE"
	done | (
		SIZE="0"
		while read FILE ; do
			TEMP=$(du -bs "$FILE" | cut -f 1)
			echo "$FILE: $TEMP bytes"
			SIZE="$TEMP + $SIZE"
		done
		expr $SIZE >&9
	) 9>&1 >&8 )

# The descriptors 8 and 9 can now be reused.

echo "Total: $TOTAL bytes"
When you have more than one stream of data, you will have to use files.

There is a trick to temporary files too. In fact, using this trick, I prefer temporary files over the pipe swap above.

Instead of using individual temporary files (named in separate variables), create a safe temporary directory, and tell the POSIX shell to remove it automatically when the script exits. That way, you never need to worry about cleaning it up, and you can use any number of temporary files -- and use descriptive names for them, too.

Code:
#!/bin/sh

# Create a safe autodeleted temporary directory.
WORK=$(mktemp -d) || exit $?
trap "rm -rf '$WORK'" EXIT

# Your script can now use any file under "$WORK",
# for example "$WORK/file-list" or "$WORK/status"
# as a temporary file, and never worry about
# removing any of them. The shell will remove
# it automatically when this script exits.
Please note the quoting used for the trap. Because the outermost quotes are double quotes, $WORK will be evaluated immediately, and not when the trap triggers. This is important! This way, even if you unset or change $WORK you will not affect the trap at all, it will still remove the original directory. I find this extremely easy to use.

I hope some of you find this useful.
 
Old 07-23-2011, 05:38 PM   #7
MTK358
LQ 5k Club
 
Registered: Sep 2009
Posts: 6,443
Blog Entries: 3

Rep: Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723
Quote:
Originally Posted by Nominal Animal View Post
Code:
#!/bin/sh

...

$( ... )

...
I thought that $(...) is bash-specific syntax.
 
Old 07-23-2011, 10:00 PM   #8
tuxdev
Senior Member
 
Registered: Jul 2005
Distribution: Slackware
Posts: 2,012

Rep: Reputation: 115Reputation: 115
Quote:
I thought that $(...) is bash-specific syntax.
It's actually POSIX, though some /bin/sh are actually Bourne and don't support it (damn you, Solaris).
 
Old 07-24-2011, 03:54 PM   #9
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948
Yup, it is in POSIX. If you want the temporary directory to work with practically all shells, just replace $(...) with `...` -- I think even Bourne shells have trap ... EXIT support. At least the SunOS 5.10 one does.

Note that the descriptor swap trick often gets complicated with backticks, since backticks cannot be nested. (POSIX does allow it if you escape the inner pair(s), but the ancient /bin/sh do not support that.) Complicated is not impossible, though. You simply need to use a function to replace the nested backtick expression. This is the most complicated example above, but rewritten to work with both POSIX shells, and ancient /bin/sh's:
Code:
#!/bin/sh

setSIZES () {
    SIZES=`LANG=C LC_ALL=C ; ls -ldG "$@" | awk '{ s+=$4 } END { print s }'`
    export SIZES
}

# Use 9 as the result descriptor,
# and 8 as the temporary output descriptor.
exec 8>&1 9>&1

TOTAL=`
	for FILE in "$@" ; do
		echo "$FILE"
	done | (
		SIZE="0"
		while read FILE ; do
			setSIZES "$FILE"
			echo "$FILE: $SIZES bytes"
			SIZE="$SIZES + $SIZE"
		done
		expr $SIZE >&9
	) 9>&1 >&8
       `

# The descriptors 8 and 9 can now be reused.

echo "Total: $TOTAL bytes"
Basically, setSIZES runs ls in a subshell, then uses awk to sum the file sizes. On SunOS 5.10 at least, du does not support the -b (size in bytes) option. The sum is saved in SIZES in the caller's environment. (It will not propagate to parent shells, so the value is only available in the immediate caller's environment. If you were to use the function inside backticks, the value is also available within the backticked expression. Like I said: complicated.)

Finally, the above scripts were not by any means the optimal way to count file sizes at all. They just illustrate the swap-output-descriptor communications method. For counting file sizes in bytes portably, I'd use something like
Code:
#!/bin/sh

if [ ":$*" = ":" ] || [ ":$*" = ":-h" ] || [ ":$*" = ":--help" ]; then
    exec >&2
    echo ""
    echo "Usage: $0 [ -h | --help ]"
    echo "       $0 files .."
    echo ""
    exit 0
fi

LANG=C
LC_ALL=C
export LANG LC_ALL

ls -ldGF "$@" | awk '
  ($0 !~ /\/$/ && NF > 7) {
    size=$4
    name=$0
    sub(/^[\t ]*[^\t ]+[\t ]+[^\t ]+[\t ]+[^\t ]+[\t ]+[^\t ]+[\t ]+[^\t ]+[\t ]+[^\t ]+[\t ]+[^\t ]+[\t ]/, "", name)
    sub(/[*\/>|@=]$/, "", name)
    total+=size
    printf("%12.0f\t%s\n", size, name)
  }
  END {
    printf("%12.0f\n", total)
  }'
Since ls -ldGF is fairly portable, producing the same output in most OSes when using the C locale, the above should be fairly reliable. It will b0rk if the user name contains a space, or if the file name contains a newline, though. It does work for both SunOS 5.10 /bin/sh (a Bourne shell) and dash-0.5.5.1 (a POSIX shell). The awk scriptlet uses the sub() function to remove the fields prior to the file name, because otherwise any whitespaces in the file name will be lost. (This should works with gawk, nawk, mawk, and even xpg4 awk.)
This is to say that if something feels overly complicated, there usually is some other tool you can use instead. Here, I replaced du with ls for compatibility reasons, and used awk for both the processing and output. It is much faster than any shell loop, too.
 
Old 11-08-2011, 11:57 AM   #10
frenchn00b
Senior Member
 
Registered: Jun 2007
Location: E.U., Mountains :-)
Distribution: Debian, Etch, the greatest
Posts: 2,561

Original Poster
Rep: Reputation: 57
Quote:
Originally Posted by grail View Post
Your issue is down to the fact that you feed your loop through a subshell (ie you pipe the data in). Try feeding the values / files directly into the loop with
redirection:
Code:
while read i
do
    <your stuff here>
done<"$HOME/.fvwmoscfg/fvwmburnerlist.lst"
You should now find all variables from within the loop are available outside

thank you it worked great.

I did before VARIABLE=` cat "$HOME/.fvwmoscfg/fvwmburnerlist.lst"`

would you know how to implement variable into the command above you gave me, so that we do not need to write on hdisk?
 
Old 11-09-2011, 01:58 AM   #11
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,007

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
Quote:
so that we do not need to write on hdisk?
Please explain what you mean by the above statement?
 
Old 11-09-2011, 02:09 PM   #12
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Arch + Xfce
Posts: 6,852

Rep: Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037
In a posix shell you can use a heredoc for input, expanding the variable(s) inside it.
Code:
#!/bin/sh

text='foo
bar
baz
bum'

while read line; do

     echo "$line"

done <<LIMITSTRING
$text
LIMITSTRING
You can use command substitutions too, of course, such as `cat *.txt`.

Last edited by David the H.; 11-09-2011 at 02:18 PM. Reason: addendum
 
Old 11-09-2011, 03:23 PM   #13
Reuti
Senior Member
 
Registered: Dec 2004
Location: Marburg, Germany
Distribution: openSUSE 15.2
Posts: 1,339

Rep: Reputation: 260Reputation: 260Reputation: 260
Quote:
Originally Posted by David the H. View Post
Code:
#!/bin/sh

text='foo
bar
baz
bum'

while read line; do

     echo "$line"

done <<LIMITSTRING
$text
LIMITSTRING
Variables can also be used directly by a triple <:
Code:
#!/bin/sh

text='foo
bar
baz
bum'

while read line; do

     echo "$line"

done <<<"$text"
 
1 members found this post helpful.
Old 11-10-2011, 12:48 AM   #14
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Arch + Xfce
Posts: 6,852

Rep: Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037
Quote:
Originally Posted by Reuti View Post
Variables can also be used directly by a triple <:
True, except that this discussion is specifically about posix-compliant scripts. The here string is a bash extension, whereas here documents are supported everywhere.

http://mywiki.wooledge.org/HereDocument

See here for a good description of the subshell problem and its workarounds:

http://mywiki.wooledge.org/BashFAQ/024
 
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
how can i create a global variable called "dogpatch" with a value of "woof" earthdog Linux - Newbie 9 12-15-2009 01:03 AM
How to get the "data type" of an "unknown variable" in "C Language" ? Affair Programming 8 06-20-2009 12:30 PM
"Permission denied" and "recursive directory loop" when searching for string in files mack1e Linux - Newbie 5 06-12-2008 07:38 AM
"Broken" envirnment variable (MANPATH) and "/etc/profile.d" question. ErV Slackware 3 03-20-2007 09:42 AM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 08:58 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration