I have a bash script which wants to do some work in parallel, I did this by putting each job in an subshell which is run in the background. While the number of jobs running simultaneously should under some limit, I achieve this by first put some lines in a FIFO, then just before forking the subshell, the parent script is required to read a line from this FIFO. Only after it gets a line can it fork the subshell. Up to now, everything works fine. But when I tried to read a line from the FIFO in the subshell, it seems that only one subshell can get a line, even if there are apparently more lines in the FIFO. So I wonder why other subshell(s) cannot read a line even when there are more lines in the FIFO.
My testing code looks something like this:
#!/bin/sh
fifo_path="/tmp/fy_u_test2.fifo"
mkfifo $fifo_path
#open fifo for r/w at fd 6
exec 6<> $fifo_path
process_num=5
#put $process_num lines in the FIFO
for ((i=0; i<${process_num}; i++)); do
echo "$i"
done >&6
delay_some(){
local index="$1"
echo "This is what u can see. $index \n"
sleep 20;
}
#In each iteration, try to read 2 lines from FIFO, one from this shell,
#the other from the subshell
for i in 1 2
do
date >>/tmp/fy_date
#If a line can be read from FIFO, run a subshell in bk, otherwise, block.
read -u6
echo " $$ Read --- $REPLY --- from 6 \n" >> /tmp/fy_date
{
delay_some $i
#Try to read a line from FIFO, __ only one subshell succeeds the following line. __
read -u6
echo " $$ This is in child # $i, read --- $REPLY --- from 6 \n" >> /tmp/fy_date
} &
done
And the output file /tmp/fy_date has content of:
Mon Apr 26 16:02:18 CST 2010
32561 Read --- 0 --- from 6 \n
Mon Apr 26 16:02:18 CST 2010
32561 Read --- 1 --- from 6 \n
32561 This is in child # 1, read --- 2 --- from 6 \n
There, I expect a line like this:
32561 This is in child # 2, read --- 3 --- from 6 \n
But it never appears, and the child #2 process is blocked there until I issue:
echo something > /tmp/fy_u_test2.fifo
Is it possible there is some buffering going on of your write to the fifo? If you have unbuffer available, could you try prefacing the echos with that? I don't really see how it could happen here but the symptoms fit so its worth a shot.
Keep in mind that a FIFO on POSIX systems is essentially a named pipe. In order to move data around on a pipe, one side needs a reader and the other side needs a writer, and when one is closed the other loses usefulness.
In other words, you cannot cat on a fifo after some other reader has exited, because the contents of the FIFO will be gone.
You may want to see about using a normal file (and use file locking to ensure that you are synchronizing your access to that normal file), or use a directory with multiple files in it, or even use shared memory or something similar to that (perhaps not in a shell script, though). It all depends on what your end-goal is, really, what the best way to go about it would be.
Seems something to do with 'read -u6' shell call. If I have STDIN of the shell closed, when 'read -u6' is issued, it tries to read 128 bytes from fd 6. But if STDIN is left untouched, when 'read -u6' is issued, it reads the bytes one by one until a '\n' is encountered. I discovered this weird action from 'strace', where in the first case, a 'read -u6' call caused the following syscall:
read(6, "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n"..., 128) = 50
and in the latter case, a 'read -u6' call caused the following syscall:
30371 16:27:15 read(6, "0", 1) = 1
30371 16:27:15 read(6, "\n", 1) = 1
Testing code follows:
#!/bin/bash
fifo_path="/tmp/fy_u_test2.fifo"
mkfifo $fifo_path
#open fifo for r/w at fd 6
exec 6<> $fifo_path
#comment or decomment the following line makes difference
exec 0>&-
process_num=20
#put $process_num lines in the FIFO
for ((i=0;i<${process_num};i++));do
echo "$i"
done >&6
delay_some(){
local index="$1"
echo "This is what u can see. $index \n"
sleep 10;
}
#In each iteration, try to read 2 lines from FIFO, one from this shell,
#the other from the subshell
for i in 1 2 3
do
date >>/tmp/fy_date
#If a line can be read from FIFO, run a subshell in bk, otherwise, block.
read -u6
echo " $$ Read --- $REPLY --- from 6 \n" >> /tmp/fy_date
{
delay_some $i
#Try to read a line from FIFO
# read -u6
echo " $$ This is in child # $i, read --- $REPLY --- from 6 \n" >> /tmp/fy_date
echo " $$ Again this is in child # $i, read --- $REPLY --- from 6 \n" >> /tmp/fy_date
echo "$i xx" >&6
# echo xx >&6
} &
done
#sleep 13
#wait
#read -u6
echo "$$ After fork, in parent, read --- $REPLY --- from 6 \n" >> /tmp/fy_date
I get all four lines in the log file when I run it. What happens if you change your shebang to #!/bin/bash?
It could be a concurrency issue, with both subshells trying to read from the same fifo at the same time. Does it happen all the time?
You could try adding a flock -x 6 statement or change the delay for the two subshells and see what happens.
BTW, I can confirm that with bash 3.2 and kernel 2.6.28 your code works fine.
I found data left unread in the FIFO when the parent shell exits got lost as the parent exits.
If I have the following code:
#!/bin/sh
fifo_path="/tmp/fy_u_test2.fifo"
mkfifo $fifo_path
#open fifo for r/w at fd 6
exec 6<> $fifo_path
process_num=9
#put $process_num lines in the FIFO
for ((i=0;i<${process_num};i++));do
echo "$i"
done >&6
for i in 1 2 3;
do
read -u6
done
After this code ends, a command 'cat /tmp/fy_u_test2.fifo' gives nothing.
BUT if I have the following code.
#!/bin/sh
fifo_path="/tmp/fy_u_test2.fifo"
mkfifo $fifo_path
#open fifo for r/w at fd 6
exec 6<> $fifo_path
process_num=9
#put $process_num lines in the FIFO
for ((i=0;i<${process_num};i++));do
echo "$i"
done >&6
for i in 1 2 3;
do
read -u6
done
#__ notice this line __
sleep 60
After issuing this code to run, during its sleeping 60sec, a command 'cat /tmp/fy_u_test2.fifo' gives the following output:
$ cat /tmp/fy_u_test2.fifo
3
4
5
6
7
8
For reasons explained in other answers here you do not want a pipe unless you can read and write from the pipe at the same time.
It is therefore advisable to use another means of IPC or restructure your usage of fifos such that an asynchronous process fills up the pipe while the main process creates work processes (or the other way around).
Here's a method of getting what you want using a simple file as a sort of queue:
#!/usr/bin/env bash
stack=/tmp/stack
> "$stack"
# Create an initial 5 spots on the stack
for i in {1..5}; do
echo >> "$stack"
done
for i in {1..10}; do
# Wait for a spot on the stack.
until read; do sleep 1; done
{
echo "Starting process #$i"
sleep $((5 + $i)) # Do something productive
echo "Ending process #$i"
# We're done, free our spot on the stack.
echo >> "$stack"
} &
done < "$stack"
Sidenote: This method isn't ideal for unlimited work since it adds a byte to the stack file for each process it invokes meaning the stack file grows slowly.
Related
Considering the following example, emulating a command which gives output after 10 seconds: exec 5< <(sleep 10; pwd)
In Solaris, if I check the file descriptor earlier than 10 seconds, I can see that it has a size of 0 and this tells me that it hasn't been populated with data yet. I can simply check every second until the file test condition is met (different from 0) and then pull the data:
while true; do
if [[ -s /proc/$$/fd/5 ]]; then
variable=$(cat <&5)
break
fi
sleep 1
done
But in Linux I can't do this (RedHat, Debian etc). All file descriptors appear with a size of 64 bytes no matter if they hold data or not. For various commands that will take a variable amount of time to dump their output, I will not know when I should read the file descriptor. No, I don't want to just wait for cat <&5 to finish, I need to know when I should perform the cat in the first place. Because I am using this mechanism to issue simultaneous commands and assign their output to corresponding file descriptors. As mentioned already, this works great in Solaris.
Here is the skeleton of an idea :
#!/bin/bash
exec 5< <(sleep 4; pwd)
while true
do
if
read -t 0 -u 5 dummy
then
echo Data available
cat <&5
break
else
echo No data
fi
sleep 1
done
From the Bash reference manual :
If timeout is 0, read returns immediately, without trying to read and
data. The exit status is 0 if input is available on the specified file
descriptor, non-zero otherwise.
The idea is to use read with -t 0 (to have zero timeout) and -u 5 (read from file descriptor 5) to instantly check for data availability.
Of course this is just a toy loop to demonstrate the concept.
The solution given by User Fred using only bash builtins works fine, but is a tiny bit non-optimal due to polling for the state of a file descriptor. If calling another interpreter (for example Python) is not a no-go, a non-polling version is possible:
#! /bin/bash
(
sleep 4
echo "This is the data coming now"
echo "More data"
) | (
python3 -c 'import select;select.select([0],[],[])'
echo "Data is now available and can be processed"
# Replace with more sophisticated real-world processing, of course:
cat
)
The single line python3 -c 'import select;select.select([0],[],[])' waits until STDIN has data ready. It uses the standard select(2) system call, for which I have not found a direct shell equivalent or wrapper.
These are my test files:
std-test.sh:
#!/bin/bash
for i in {0..20}
do
number=$RANDOM
let "number %= 10"
if [ $number -le 6 ]
then
echo $i;
else
echo $i 1>&2;
fi
done
process.sh:
#!/bin/bash
while read line; do
[[ $1 = "err" ]] && echo "$(date +%s%3N) $line" >> stderr;
[[ $1 = "out" ]] && echo "$(date +%s%3N) $line" >> stdout;
done
std-test.sh creates twenty lines containing their line number, and process.sh is able to read another commands output when piped to it, while it saves to stderr when err is passed to it as argument, and to stdout when out is passed. The command I use to test the whole thing is:
./std-test.sh 2> >(./process.sh err) > >(./process.sh out) (taken from here). My test outputs are as follows:
stdout:
1486297129986 0
1486297129987 1
1486297129988 2
1486297129988 3
1486297129989 4
1486297129990 6
1486297129991 7
1486297129991 8
1486297129992 9
1486297129993 10
1486297129993 11
1486297129994 12
1486297129995 14
1486297129995 17
1486297129996 18
stderr:
1486297129986 5
1486297129987 13
1486297129987 15
1486297129988 16
1486297129989 19
1486297129990 20
When I tried parsing the output, I realized, that the order of stderr and stdout is completely messed up. Why does 5 in stderr come before 1 in stdout, for instance? Or 13 right after 1? What's the mistake I made here, and how could I resolve it?
Edit #1: I know I could pipe stderr and stdout directly to files, but process.sh will curl the results to a remote server. Saving it to files is just kind of a POC.
Edit #2: I am actually trying to timestamp and process docker logs programmatically.
Update:
From the comments it appears OP was really trying to timestamp and log the output from an application running in a docker container.
docker logs -t already does this and is the best tool for job.
Original answer:
Probably because you're timestamping each line with the time it is processed and not the time they're actually generated?
You launch 3 processes when you run that command, not all of them run in parallel.
The processes aren't running in a truly parallel fashion, they can get preempted whenever the kernel feels like. Because the stdout is longer/processing more lines, it is more likely to get preempted and then resumed at a later point. Which would explain the timestamping.
What do you really need the timestamps on the lines for? Do you simply want ordering between the lines, or do you want real true timestamps?
Edit: as #Sven Festersen pointed out in the comments, it also might be because stdout is buffered when piped by default.
I am trying to figure out a way to monitor the files I am dumping from my script. If there is no increment seen in child files then kill my script.
I am doing this to free up the resources when not needed. Here is what I think of , but I think my apporch is going to add burden to CPU. Can anyone please suggest more efficent way of doing this?
Below script is suppose to poll in every 15 sec and collect two file size of same file, if the size of the two samples are same then exit.
checkUsage() {
while true; do
sleep 15
fileSize=$(stat -c%s $1)
sleep 10;
fileSizeNew=$(stat -c%s $1)
if [ "$fileSize" == "$fileSizeNew" ]; then
echo -e "[Error]: No activity noted on this window from 5 sec. Exiting..."
kill -9 $$
fi
done
}
And I am planning to call it as follow (in background):
checkUsage /var/log/messages &
I can also get solution if, someone suggest how to monitor tail command and if nothing printing on tail then exit. NOT SURE WHY PEOPLE ARE CONFUSED. End goal of this question is to ,check if the some file is edited in last 15 seconds. If not exit or throw some error.
I have achived this by above script,but I don't know if this is the smartest way of achiveing this. I have asked this question to know views from other if there is any alternative way or better way of doing it.
I would based the check on file modification time instead of size, so something like this (untested code):
checkUsage() {
while true; do
# Test if file mtime is 'second arg' seconds older than date, default to 10 seconds
if [ $(( $(date +"%s") - $(stat -c%Y /var/log/message) )) -gt ${2-10} ]; then
echo -e "[Error]: No activity noted on this window from ${2-10} sec. Exiting..."
return 1
fi
#Sleep 'first arg' second, 15 seconds by default
sleep ${1-15}
done
}
The idea is to compare the file mtime with current time, if it's greater than second argument seconds, print the message and return.
And then I would call it like this later (or with no args to use defaults):
[ checkusage 20 10 ] || exit 1
Which would exit the script with code 1 as when the function return from it's infinite loop (as long as the file is modified)
Edit: reading me again, the target file could be a parameter too, to allow a better reuse of the function, left as an exercise to the reader.
If on Linux, in a local file system (Ext4, BTRFS, ...) -not a network file system- then you could consider inotify(7) facilities: something could be triggered when some file or directory changes or is accessed.
In particular, you might have some incron job thru incrontab(5) file; maybe it could communicate with some other job ...
PS. I am not sure to understand what you really want to do...
I suppose an external programme is modifying /var/log/messages.
If this is the case, below is my script (with minor changes to yours)
#Bash script to monitor changes to file
#!/bin/bash
checkUsage() # Note that U is in caps
{
while true
do
sleep 15
fileSize=$(stat -c%s $1)
sleep 10;
fileSizeNew=$(stat -c%s $1)
if [ "$fileSize" == "$fileSizeNew" ]
then
echo -e "[Notice : ] no changes noted in $1 : gracefully exiting"
exit # previously this was kill -9 $$
# changing this to exit would end the program gracefully.
# use kill -9 to kill a process which is not under your control.
# kill -9 sends the SIGKILL signal.
fi
done
}
checkUsage $1 # I have added this to your script
#End of the script
Save the script as checkusage and run it like :
./checkusage /var/log/messages &
Edit :
Since you're looking for better solutions I would suggest inotifywait, thanks for the suggestion from the other answerer.
Below would be my code :
while inotifywait -t 10 -q -e modify $1 >/dev/null
do
sleep 15 # as you said the polling would happen in 15 seconds.
done
echo "Script exited gracefully : $1 has not been changed"
Below are the details from the inotifywait manpage
-t <seconds>, --timeout <seconds> Exit if an appropriate event has not occurred within <seconds> seconds. If <seconds> is zero (the default),
wait indefinitely for an event.
-e <event>, --event <event> Listen for specific event(s) only. The events which can be listened for are listed in the EVENTS section.
This option can be specified more than once. If omitted, all events
are listened for.
-q, --quiet If specified once, the program will be less verbose. Specifically, it will not state when it has completed establishing all
inotify watches.
modify(Event) A watched file or a file within a watched directory was
written to.
Notes
You might have to install the inotify-tools first to make use of the inotifywait command. Check the inotify-tools page at Github.
There is a script which interacts with the user by "press y / n" dialog (i.e. stdin is already in use). By pressing predefined keyboard button the main script should interrupt its work.
I tried to implement it two ways, with read and using grep (ah, and also tried using stty - stty -echo -icanon time 0 min 0, but all in all it didn`t work).
The problem with grep (grep -q ) is that the main thread goes in a loop with grep (which I looped in order to constantly check stdin), while I need the main script to move on, just listening STDIN for the specific key pressed.
With read it finally transformed into such a small piece:
breakByButton()
{
while ! [ "$z" = "11" ]
do
read -t 1 -n 1 key
if [[ $key = <desired key> ]]
then
echo -e "\n\e[31mStopped by user\e[0m"
break
fi
done
}
Of course, this function also works only where it was called. As a separate script in the background, as well as with grep, execution is interrupted after the first pressing Enter.
Now I'm thinking of the parallel process - the script and read, but haven`t got decisions yet.
While havent abosolutely no idea what you really need, try the next demo (press q for exit)
trap 'stty sane;exit' 0 2
do_quit() {
echo -e "Quit....\r" >&2
return 1
}
do_something() {
echo -e "Doing something after the key: $key\r"
}
inkey() {
stty raw
while :
do
read -t 1 -n 1 key
case "$key" in
q|Q) do_quit || exit ;;
'') ;; #nothing
*) do_something "$key" ;;
esac
done
}
#main
inkey
That is why there are interrupts. If you want to interrupt a process that is otherwise using stdin, type ctrl-C. This interrupts the process and, optionally, passes control to an interrupt handler. For example, in this script MyExit functions as the interrupts handler. If you run this script and type Ctrl-C, execution stops and your desired message will appear:
MyExit() {
echo -e "\n\e[31mStopped by user\e[0m"
exit
}
trap MyExit INT
while true
do
# do something important that captures stdin
cat >/dev/null
done
In practice, MyExit should also do whatever is needed to clean up after script execution is interrupted. This often includes, for example, deleting temporary files.
The key feature of this approach works even when stdin is already in use. It does not require waiting until stdin is available.
I have a lengthy menu script that relies on a few command outputs for it's variables. These commands take several seconds to run each and I would like to spawn new processes to set these variables. It would look something like this:
VAR1=`somecommand` &
VAR2=`somecommand` &
...
wait
echo $VAR1 $VAR2
The problem is that the processes are spawned and die with those variables they set. I realize that I can do this by sending these to a file and then reading that but I would like to do it without a temp file. Any ideas?
You can get the whole process' output using command substitution, like:
VAR1=$(somecommand &)
VAR2=$(somecommand &)
...
wait
echo $VAR1 $VAR2
This is rather clunky, but works for me. I have three scripts.
cmd.sh is your "somecommand", it is a test script only:
#!/bin/ksh
sleep 10
echo "End of job $1"
Below is wrapper.sh, which runs a single command, captures the output, signals the parent when done, then writes the result to stdout:
#!/bin/ksh
sig=$1
shift
var=$($#)
kill -$sig $PPID
echo $var
and here is the parent script:
#!/bin/ksh
trap "read -u3 out1" SIGUSR1
trap "read -p out2" SIGUSR2
./wrapper.sh SIGUSR1 ./cmd.sh one |&
exec 3<&p
exec 4>&p
./wrapper.sh SIGUSR2 ./cmd.sh two |&
wait
wait
echo "out1: $out1, out2: $out2"
echo "Ended"
2x wait because the first will be interrupted.
In the parent script I am running the wrapper twice, once for each job, passing in the command to be run and any arguments. The |& means "pipe to background" - run as a co-process.
The two exec commands copy the pipe file descriptors to fds 3 and 4. When the jobs are finished, the wrapper signals the main process to read the pipes. The signals are caught using the trap, which read the pipe for the appropriate child process, and gather the resulting data.
Rather convoluted and clunky, but it appears to work.