|
Softpanorama |
May the source be with you, but remember the KISS principle ;-)
|
Korn shell and its derivates like ksh93 and zsh ( possesses an interesting and unique feature: the ability to pipe a file into a loop. This is effectively an implementation of simple coroutines although shell has also more general form (See Learning Korn Shell by Bill Rosenblat and Arnold Robbins). In bash this capability is limited as bash does not run the last stage of the pipe as the current process... The developers probably think they can reinvent the wheel but got a square.. Googling for "bash pipe subprocesses order" shows the extent of the problem, but I couldn't find what the bash developers' official stand
Let's assume that we need to find all files that contain string "19%" which is a typical for printing commands like "19%2d"
cd/ /usr/bin
ls | while read file
do
echo $file
string $file | grep '19%'
done
Here we use the ls command to generate the list of the file names and this list it piped into a loop. In a loop we echo command and then run strings piped to grep looking for suspicious format strings.
In another example from O'Reilly "Learning Korn Shell" (first edition). Here we will pipe awk output into the loop. This is a function that, given a pathname as argument, prints its equivalent in tilde notation if possible:
function tildize {
if [[ $1 = $HOME* ]]; then
print "\~/${1#$HOME}"
return 0
fi
awk '{FS=":"; print $1, $6}' /etc/passwd |
while read user homedir; do
if [[ $homedir != / && $1 = ${homedir}?(/*) ]]; then
print "\~$user/${1#$homedir}"
return 0
fi
done
print "$1"
return 1
}
Pipes can also output data to the loopLoop can also serve as a source to input for the pipe. For example
{ while read line'?adc> '; do
print "$(alg2rpn $line)"
done
} | dc
As an example; assume that you want to go through all C files of a directory and, if they are readable to you, convert the filenames to contain lowercase letters only (this example may be a little contrived). We can do it it in slightly different ways.
The first script calls tr inside the the for-loop:
and the second script uses coroutine linage (pipe) to feed tr from the loop:#!/bin/sh for x in *.c do [ -r $x ] && echo $x | tr 'A-Z' 'a-z' done
#!/bin/sh for x in *.c do [ -r $x ] && echo $x done | tr 'A-Z' 'a-z'
pipeline of commands
We discuss in the book (in the chapter on common mistakes) the fact that a pipeline of commands runs those commands in subshells. The result (or dilema) is that what happens in those subshells (e.g. counting something) is lost to the parent shell script unless the parent captures output from the pipeline, but that isn't always easy or desirable.The bash man page describes a feature of bash called "Process Substitution" that lets you substitute the output of a pipeline of commands (actually a list of commands) using <(list) as the syntax.
But notice how the feature is described:
The process list is run with its input or output connected to a FIFO or some file in /dev/fd. The name of this file is passed as an argument to the current command as the result of the expansion.The <(...) is going to be replaced with the name of a fifo. So if you wrote:
wc <(some commands)the result would be:wc fifothat is, the fifo filename is passed to the command. That's fine for commands like wc that can accept a filename. But what about a builtin like while?
It turns out that you can add the redirect from the fifo, but the space between the two less-than signs is crucial to distinguish it from "<<", the "here document" syntax.
So you can write:
while read a b c do ... done < <(pipeline of commands)
Piping output to a read, using echo to set variables will fail.Yet, piping the output of cat seems to work.
cat file1 file2 | while read line do echo $line doneHowever, as Bjön Eriksson shows:
Example 14-8. Problems reading from a pipe
#!/bin/sh # readpipe.sh # This example contributed by Bjon Eriksson. last="(null)" cat $0 | while read line do echo "{$line}" last=$line done printf "\nAll done, last:$last\n" exit 0 # End of code. # (Partial) output of script follows. # The 'echo' supplies extra brackets. ############################################# ./readpipe.sh {#!/bin/sh} {last="(null)"} {cat $0 |} {while read line} {do} {echo "{$line}"} {last=$line} {done} {printf "nAll done, last:$lastn"} All done, last:(null) The variable (last) is set within the subshell but unset outside.The gendiff script, usually found in /usr/bin on many Linux distros, pipes the output of find to a while read construct.
find $1 \( -name "*$2" -o -name ".*$2" \) -print | while read f; do . . .
It is possible to paste text into the input field of a read. See Example A-39.
ksh vs bash: setting variable in piped loops are lost
I'm a long time unix (not linux) programmer, mainly shell scripts.
Unix does not know about bash, but rather ksh or sh.
So I often build stuff like this:
the question is not how to reformulate this differently using awk or perl.Code:n=0 du | sort -n | while read size dir do if [ "$size" -gt 100000 ] then n=$((n+1)) fi done echo "Found $n too big files"
The question is:
in ksh this script returns the correct value
in bash it always returns 0
This is because in ksh the last command in the pipe runs in the current process, whereas in bash the first command runs in the current proces. Result: the modified variables are lost.
I'm still puzzled that his is the case and that nobody really cares about.
Maybe I'm missing the point and there is some simple environment variable or other setting to change to enable ksh compatible
Shell Script Pearls - Google Book Search
My Favorite bash Tips and Tricks
Copyright © 1996-2007 by Dr. Nikolai Bezroukov. www.softpanorama.org was created as a service to the UN Sustainable Development Networking Programme (SDNP) in the author free time. Submit comments This document is an industrial compilation designed and created exclusively for educational use and is placed under the copyright of the Open Content License(OPL). Original materials copyright belong to respective owners. Quotes are made for educational purposes only in compliance with the fair use doctrine.
Standard disclaimer: The statements, views and opinions presented on this web page are those of the author and are not endorsed by, nor do they necessarily reflect, the opinions of the author present and former employers, SDNP or any other organization the author may be associated with. We do not warrant the correctness of the information provided or its fitness for any purpose.
Last modified: April 04, 2008