From: Chris F.A. Johnson on
On 2010-01-16, mop2 wrote:
> On Fri, 15 Jan 2010 18:48:08 -0200, Robert Latest <boblatest(a)yahoo.com> wrote:
>
>> Hello people,
>>
>> the snippet from an interactive bash session below should illustrate my
>> "problem". Stuff on the right-hand side are my comments for the
>> respective line. I attempted assigning hours and minutes from date's
>> output to a couple of variables using 'read':
>>
>>
>> $ date '+%H +%M' | read h m ; echo $h $m | First try
>> | ... prints empty line
>> $ read h m ; echo $h $m | Huh? Can't 'read' read from stdin??
>> 10 20 | This was typed in by hand
>> 10 20 | Sure enough: correct output
>> $ cat | read h m ; echo $h $m | Let's pipe stdin through cat
>> 11 22 | This was typed in
>> 10 20 | h and m still have the old values
>> $ eval `date '+h=%H; m=%M'` ; echo $h $m | This is what I ended up doing
>> 21 38 | ... with the correct result
>>
>> In short: Why doesn't 'read' assign any values to variables h and m
>> when stdin comes in from a pipe?
>
>
> As already said by others, your read is running in a subshell.
> Pass the read command to the current shell:
>
> read h m <<<`date '+%H %M'`

If you want to test across different shells, use the portable syntax:

read h m <<EOF
`date '+%H %M'`
EOF

> As reference, a quick test with various shell:
>
> $ for s in /bin/*sh;do printf "$s \t:";$s -c 'printf "\t$h $m\t:\t";read h m <<<`date "+%H %M"`;echo $h $m';done
> /bin/ash :Syntax error: redirection unexpected
> /bin/bash : : 22 27
> /bin/csh :Missing name for redirect.
> /bin/ksh : : 22 27
> /bin/rksh : : 22 27
> /bin/sh : : 22 27
> /bin/tcsh :Missing name for redirect.
> /bin/zsh : : 22 27
> $

csh and tcsh are a different type of shell and do not belong in a
syntax comparison with Bourne-type shells.

--
Chris F.A. Johnson, author <http://shell.cfajohnson.com/>
===================================================================
Shell Scripting Recipes: A Problem-Solution Approach (2005, Apress)
Pro Bash Programming: Scripting the GNU/Linux Shell (2009, Apress)
===== My code in this post, if any, assumes the POSIX locale =====
===== and is released under the GNU General Public Licence =====
From: Jerry Peters on
Jerry Peters <jerry(a)example.invalid> wrote:
> Robert Latest <boblatest(a)yahoo.com> wrote:
>> Hello people,
>>
>> the snippet from an interactive bash session below should illustrate my
>> "problem". Stuff on the right-hand side are my comments for the
>> respective line. I attempted assigning hours and minutes from date's
>> output to a couple of variables using 'read':
>>
>>
>> $ date '+%H +%M' | read h m ; echo $h $m | First try
>> | ... prints empty line
>> $ read h m ; echo $h $m | Huh? Can't 'read' read from stdin??
>> 10 20 | This was typed in by hand
>> 10 20 | Sure enough: correct output
>> $ cat | read h m ; echo $h $m | Let's pipe stdin through cat
>> 11 22 | This was typed in
>> 10 20 | h and m still have the old values
>> $ eval `date '+h=%H; m=%M'` ; echo $h $m | This is what I ended up doing
>> 21 38 | ... with the correct result
>>
>> In short: Why doesn't 'read' assign any values to variables h and m
>> when stdin comes in from a pipe?
>>
>> Thanks,
>> robert
>
> Read does assign the values, the problem is that read is running in a
> child shell which cannot affect the values of h and m in the parent
> shell. Try a here document:
>
> read h m <<-EOF
> date '+%H %M'
> EOF
>
> Process substitution should also work.
>
Oops, that should be:

read h m <<-EOF
$(date '+%H %M')
EOF

Jerry
From: Robert Latest on
mop2 wrote:
> As already said by others, your read is running in a subshell.
> Pass the read command to the current shell:
>
> read h m <<<`date '+%H %M'`

I've also found that this works:

date +'%H %M' | ( read h m ; echo $h $m )

Well... it works in the sense that I get echoed what I wanted echoed for
the test, but of course this construct still doesn't get the values into
the environment where they are needed.

I don't understand, though, why "read" and "echo" are in different
shells when the parentheses are left out.

robert

From: Icarus Sparry on
On Mon, 18 Jan 2010 17:19:44 +0000, Robert Latest wrote:

> mop2 wrote:
>> As already said by others, your read is running in a subshell. Pass the
>> read command to the current shell:
>>
>> read h m <<<`date '+%H %M'`
>
> I've also found that this works:
>
> date +'%H %M' | ( read h m ; echo $h $m )
>
> Well... it works in the sense that I get echoed what I wanted echoed for
> the test, but of course this construct still doesn't get the values into
> the environment where they are needed.
>
> I don't understand, though, why "read" and "echo" are in different
> shells when the parentheses are left out.
>
> robert

Let me see if I can explain.

Start with the process that is running the script. Let us assume that it
is process number 100. It sees the line "date +'%H %D' | something".

For shell implementations like bash, it notices that this is not a shell
builtin, so it has to run another program, so it calls "fork" to create a
new process, let us say 101. 101 is given the task of running the command.

101 notices that there is a pipe, so it calls "pipe" to create a pipe,
and then calls "fork" to create a new process to run the left hand side
of the pipe. Let us say this creates process 102. Processes 101 and 102
adjust their file descriptors so that stdout of 102 goes to the pipe, and
stdin of 101 comes from the pipe. Then 102 calls "exec" to replace itself
with the "date" command, and 101 goes on to execute the "something".

**** The "date | read ; echo" case
If the "something" is "read h m" then 101 does the read (once 102 - now
date- has produced the values), and then exits, discarding the values of
h & m. At this point 100, which has been waiting for 101 to finish, now
reads the next command which is "echo $h $m". It then processes that.

**** The "date | { read ; echo ; }" case
The "something" is "read h m ; echo $h $m" (the { and } were just used to
group the commands, and are no longer needed). Process 101 says "read",
this is a builtin, so I will run it. It sets the variables h and m. It
then moves onto the next command, which is "echo $h $m", and it runs
that, outputting the values you want. At this point 101 has reached the
end of its commands, so it exits. 100, which has been waiting for 101,
picks up the script at the next line.

In your case you are using ( and ), rather than { and }. This may cause
another shell to be invoked, so instead of 101 doing the read and echo, a
new shell (say 104) might do it. The important point is that with the
grouping the read and echo are done in the same shell, whilst without the
grouping then in shell implementations like bash they are done in
different shells.
From: mop2 on
On Mon, 18 Jan 2010 15:19:44 -0200, Robert Latest <boblatest(a)yahoo.com> wrote:

> mop2 wrote:
>> As already said by others, your read is running in a subshell.
>> Pass the read command to the current shell:
>>
>> read h m <<<`date '+%H %M'`
>
> I've also found that this works:
>
> date +'%H %M' | ( read h m ; echo $h $m )
>
> Well... it works in the sense that I get echoed what I wanted echoed for
> the test, but of course this construct still doesn't get the values into
> the environment where they are needed.
>
> I don't understand, though, why "read" and "echo" are in different
> shells when the parentheses are left out.
>
> robert

In your example above both sides of the pipe "are" in the same shell
(a binary program on memory), but the right side of the pipe is in a
subshell of its own. An isolated area which receive data from the
parent shell, but doesn't send nothing to it, except the return status
(sorry, AFAIK).
If I am wrong, someone let me know...

Well, the shell can explain this better than me.

A subshell is not always an other kernel process:
$ eval echo \$0 \$$
-bash 1504
$ { eval echo \$0 \$$;}
-bash 1504
$ (eval echo \$0 \$$)
-bash 1504
$ cat< <(eval echo \$0 \$$)
-bash 1504
$ echo `eval echo \$0 \$$`
-bash 1504
$ echo $(eval echo \$0 \$$)
-bash 1504
$ bash -c 'eval echo \$0 \$$'
bash 16500
$ bash <<<'eval echo \$0 \$$'
bash 17253

You can see, a similar behavior about your question,
using a function in bash.

An "isolated process" (process of the shell) for the variable "r":
$ echo =$r=
==
$ f(){ local r; read r;}
$ f <<<isolated
$ echo =$r=
==

Now the shell uses the value of the "r" (default behavior):
$ f(){ read r;}
$ echo =$r=
==
$ f <<<r_not_local
$ echo =$r=
=r_not_local=
$

bash's man page perhaps can help a bit more, seek for
"COMMAND EXECUTION ENVIRONMENT"
First  |  Prev  |  Next  |  Last
Pages: 1 2 3 4
Prev: Mutt: unable to attach file
Next: Why NonZero Exit Status