"
hongy...@gmail.com" <
hongy...@gmail.com>:
> On Wednesday, April 6, 2022 at 3:38:47 AM UTC+8, Helmut Waitzmann wrote:
>> "
hongy...@gmail.com" <
hongy...@gmail.com>:
>>> On Ubuntu 20.04.3 LTS, I check the self-compiled git master
>>> version of xclip with the following bash script:
>>>
>>> ```
>>> #! /bin/bash
>>> myclip() {
>>> xclip -selection clipbord -in <<< "hello world"
>>> echo x
>>> return
>>> }
>>> set -x
>>> while true; do
>>> y="$(myclip)"
>>> done
>>> ```
>>>
>>> It will be blocked there forever, as shown below:
>>>
>>>
>>> ```
>>> $ bash myclip.sh
>>> + true
>>> ++ myclip
>>> ++ xclip -selection clipbord -in
>>> ++ echo x
>>> ++ return
>>> ^C
>>> ```
>>>
>>> In other words, when running xclip in a function, it cannot
>>> return to the main script.
>
> I still don't understand
>
I'll try to explain:
The various variants of your example I gave in the messages with
the Message‐Ids <
83h777t...@helmutwaitzmann.news.arcor.de> and
<
83a6czt...@helmutwaitzmann.news.arcor.de> show: It's not the
function that causes the blocking. The script blocks if and only
if "xclip" is invoked without redirecting its standard output away
from feeding the command substitution "$( … )". Whether that
occurs inside of a function or just inline makes no difference.
So, what's going on, there?
When the shell performs a command substition
"$( some commands )",
it first creates and opens a pipe by means of the system call
"pipe()". (A pipe is a kernel object which can be written into at
one end and read from at the other end. See the manual pages
pipe(7) and pipe(2).)
Then the shell forks a process that will execute the subshell
environment: the command line inside the "$( )".
The forked subshell environment will write to the created pipe
while the outer shell, i. e. the shell that has forked the subshell
environment, will continue reading data from the created pipe and
collecting it until it reaches an end‐of‐file condition. When it
has reached the end‐of‐file condition, it will wait by means of one
of the "wait" family of system calls (see the manual page wait(2))
for the subshell environment to terminate.
It puts the collected data it has read from the pipe into the
parameter list of the command. In your example that command is the
variable assignment:
y="$( … )"
Therefore, the read data will get assigned to the shell variable
"y".
The exit status of the variable assignment will be the exit status
as obtained from the "wait" system call, i. e. it will be the exit
status of the command substitution.
The key to understanding why your example blocks is to understand
what an end‐of‐file condition in a pipe is:
When reading from a pipe, there are (at least) three conditions to
be distinguished:
(1) A writer has written something to the pipe. Then upon reading,
the reader will get that data, which will be removed from the pipe.
(2) The pipe is empty but the writing end is still open for
writing, i.e. nothing has been written to it since all data that
may have been written to it have been read. Then upon reading, the
reader will block and wait for further data to arrive as it does
not know whether the writer eventually will write further data.
(3) The writer may have written some (or may not have written any)
data to the pipe and then closes the writing end of the pipe. When
there is no writer left to write to the writing end of the pipe,
then upon reading, the reader will get the data (if there is one)
like in (1) and then it will get an end‐of‐file condition.
The end‐of‐file condition tells the reader (in your use case: the
outer shell) that there will no further data arrive in the pipe (as
no one has it open for writing any more).
In the use case of the command substitution the outer shell will
know that it has read all data to be used in the command
substitution. It will close its reading end of the pipe and then
wait for the subshell environment to terminate.
Now what's going on, when the subshell environment starts the
"xclip" command which will fork a subprocess?
Without redirection of its standard output, that is, the blocking
use cases, "xclip" will have its standard output file descriptor be
the writing end of the pipe. When "xclip" forks its subprocess, the
subprocess will of course inherit the writing end of the pipe. =>
There are two processes which might write to the pipe.
Now, if the "outer" "xclip" process terminates, its writing end of
the pipe will implicitly get closed, but the writing end of the pipe
hold by the forked "xclip" subprocess will remain open.
=> As written above in (2) and (3) the reader of the pipe won't get
an end‐of‐file condition as long as the forked "xclip" subprocess
neither closes its standard output (Of course it will not: No
program is expected to close its standard output.) nor terminates.
=> Therefore the outer shell will wait for the subshell environment
to send an end‐of‐file condition to the pipe until the cows come
home.
Now on the other hand, if "xclip" is called with its standard
output file descriptor redirected from the writing end of the pipe
to for example "/dev/null", then its forked subprocess will inherit
the redirected standard output file descriptor.
=> No dangling writing end of the pipe will stay open and the outer
shell will finally get its longed‐for end‐of‐file condition when
reading from the pipe.
That scenario is not specific to "xclip". Here is an example using
"sleep" in the background. Note: "sleep" won't write anything to
its standard output, and of course it won't even bother closing it:
(
while var="$(
sleep -- 2 &
date -- '+%Y-%m-%dT%T%z'
)"
do
printf 'command substitution ran at %s\n' "$var"
date -- '+command substitution ended at %Y-%m-%dT%T%z'
echo
sleep -- 1
done
)
The output shows that the command substitution lasts for 2 seconds
in spite of the "sleep" command being run in the background,
because the end‐of‐file condition for the reader is delayed until
that two‐seconds‐sleep terminates and implicitly gets it standard
output file descriptor, which refers to the writing end of the
pipe, closed.
If the standard output of the "sleep" command is redirected from
the writing end of the pipe to (for example) "/dev/null" then the
command substitution returns almost immediately, as can be seen
here:
(
while var="$(
sleep -- 2 > /dev/null &
date -- '+%Y-%m-%dT%T%z'
)"
do
printf 'command substitution ran at %s\n' "$var"
date -- '+command substitution ended at %Y-%m-%dT%T%z'
echo
sleep -- 1
done
)
May I add my refrain? Knowing linux or unix greatly helps
understanding the shell. Without that knowledge one will have a
hard job doing so.