proc
This section contains functions related to handling co-processes. The Bash builtin coproc is missing features, in particular there may be only one coproc open at any time.
This library comes with L_proc_popen which allows to open any number of child processes for writing and reading.
Usage Guide¶
The core function is L_proc_popen. It launches a command asynchronously (like coproc) but gives you a handle to manage it. This handle is the PID of the process, stored in the variable you provide.
Basic Interaction¶
The most common use case is opening a process, writing to it, and reading from it.
# Open a process (cat) that reads from stdin and writes to stdout
# -I pipe: We want to write to its stdin
# -O pipe: We want to read from its stdout
L_proc_popen -I pipe -O pipe proc cat
# Write to the process's stdin
L_proc_printf "$proc" "Hello World\n"
# Close stdin to signal EOF to the process (cat will then finish and exit)
L_proc_close_stdin "$proc"
# Read the process's output
L_proc_read "$proc" line
echo "Got: $line"
# Wait for the process to clean up
L_proc_wait "$proc"
Input/Output Modes¶
L_proc_popen is powerful because it allows precise control over standard input (-I), output (-O), and error (-E) streams. You can mix and match these modes.
Pipe (Default for interaction)¶
Use pipe when you want to interact with the stream using L_proc_printf, L_proc_read, or L_proc_communicate.
L_proc_popen -I pipe -O pipe -E pipe proc my_command
# Now you can write to proc stdin, and read from proc stdout and stderr
Fixed Input String¶
Use -I input with -i "string" to pass a fixed string to the process's stdin immediately. This is useful when you don't need to interactively write to the process.
# Pass "hello" to grep's stdin
L_proc_popen -I input -i "hello" -O pipe proc grep "h"
L_proc_communicate -o output "$proc"
echo "$output" # hello
File Redirection¶
Redirect streams directly to or from files using file. This avoids the overhead of reading/writing in the shell.
# Write stdout directly to a file
L_proc_popen -O file -o "/tmp/output.log" proc echo "log entry"
L_proc_wait "$proc"
cat /tmp/output.log
# Read stdin directly from a file
echo "data" > /tmp/input.txt
L_proc_popen -I file -i "/tmp/input.txt" -O pipe proc cat
L_proc_communicate -o output "$proc"
Discarding Output (Null)¶
Use null to redirect a stream to /dev/null.
# Ignore stderr
L_proc_popen -O pipe -E null proc command_with_noisy_stderr
Closing Streams¶
Use close to completely close the file descriptor for the process.
# Process will receive "Bad file descriptor" if it tries to write to stdout
L_proc_popen -O close proc echo "this will fail"
Existing File Descriptors¶
Use fd to connect streams to existing file descriptors open in your current shell.
# Connect process stderr to the current shell's stderr (pass-through)
L_proc_popen -E fd -e 2 proc my_command
Automatic Cleanup¶
You can register a "finally" trap to ensure the process is waited upon when your function returns, preventing zombie processes.
my_func() {
# -W 0 registers a cleanup handler for the current stack level
L_proc_popen -W 0 -I pipe -O pipe proc sleep 10
# Even if we return early or error out here, the process will be cleaned up
}
Managing Multiple Processes¶
Since L_proc_popen uses variables to store handles, you can manage multiple processes easily using arrays.
#!/usr/bin/env bash
. $(dirname "$0")/../L_lib.sh
L_finally wait
L_finally L_kill_all_childs
childs=()
for script in 'sleep 1 && exit 1' 'sleep 2; exit 2' 'sleep 3; exit 3'; do
L_proc_popen tmp bash -c "$script"
childs+=("$tmp")
done
for i in "${childs[@]}"; do
L_proc_wait i
echo "Process [$(L_proc_get_cmd i)] pid $(L_proc_get_pid i) exited with $(L_proc_get_exitcode i)"
done
API Reference¶
proc
¶
Processes and jobs related functions.
L_bashpid_to
¶
Get bashpid in a way compatible with Bash before 4.0.
Argument:
$1
Variable to store the result to.
L_raise
¶
Send signal to itself.
Argument:
$@
Kill arguments. See kill --help.
L_kill_all_jobs
¶
L_wait_all_jobs
¶
L_get_all_childs
¶
Get all pids of all child processes including grandchildren recursively.
Option:
-v <var>
Argument:
[$1]
Pid of the parent. Default: $BASHPID.
See: https://stackoverflow.com/a/52544126/9072753
L_get_all_childs_v
¶
L_kill_all_childs
¶
Kills all childs of the pid.
Arguments:
-
-sigspecSignal to use. -
[$1]Pid of the process to kill all childs of. Defualt: $BASHPID
L_is_fd_open
¶
Check if file descriptor is open.
Argument:
$1
file descriptor
Shellcheck disable= SC2188
L_get_free_fd_to
¶
Get free file descriptors
Argument:
$@
variables to assign with the file descriptor numbers
L_pipe
¶
Open two connected file descriptors.
This internally creates a temporary file with mkfifo The result variable is assigned an array that: - [0] element is the input to the pipe, - [1] element is the output from the pipe. This is meant to mimic the pipe() C function.
Example
L_pipe tmp
L_array_extract tmp in out
echo 123 >&"$in"
exec "$in">&-
cat <&"$out"
exec "$out"<&-
Arguments:
-
<var>variable name to assign result to -
[str]template temporary filename, default: ${TMPDIR:/tmp}/L_pipe_XXXXXXXXXX
L_mkstemp
¶
Open three file descriptors read-write connected to a deleted temporary file.
This internally creates a temporary file and immidately removes it. Why three FD? Because it is not possible to rewind the file descriptor in shell, so you get extra spare ones to read from the file.
Arguments:
-
<var>variable name to assign result to -
[str]template temporary filename, default: ${TMPDIR:/tmp}/L_mkstemp_XXXXXXXXXX
L_proc_popen
¶
Process open. Coproc replacement.
The input/output options are in three groups:
- -I and -i for stdin,
- -O and -o for stdout,
- -E and -e for stderr.
Uppercase letter option specifies the mode for the file descriptor.
There are following modes available that you can give to uppercase options -I -O and -E:
- null - redirect to or from /dev/null
- close - close the file descriptor >&-
- input - -i specifies the string to forward to stdin. Only allowed for -I.
- stdout - connect file descriptor to stdout. -o or -e value are ignored.
- stderr - connect file descriptor to stderr. -o or -e value are ignored.
- pipe - create a fifo and connect file descriptor to it. -i -o or -e option specifies part of the temporary filename.
- file - connect file descriptor to file specified by -i -o or -e option
- fd - connect file descriptor to another file descriptor specified by -i -o or -e option
There first argument specifies an output variable that will be assigned the PID.
Several global array variables hold additional information about the process:
_L_PROC_EXIT[PID]- Exitcode or empty if not yet finished._L_PROC_FD0[PID]- If -Ipipe the file descriptor connected to stdin of the program, otherwise empty._L_PROC_FD1[PID]- If -Opipe the file descriptor connected to stdout of the program, otherwise empty._L_PROC_FD2[PID]- If -Epipe the file descriptor connected to stderr of the program, otherwise empty._L_PROC_CMD[PID]- The %q escaped command that was executed.
You should use getters L_proc_get_* to extract the data from them variable.
Example
L_proc_popen -Ipipe -Opipe -Estdout proc sed 's/w/W/g'
L_proc_printf proc "%s\n" "Hello world"
L_proc_read proc line
L_proc_wait -c -v exitcode proc
echo "$line"
echo "$exitcode"
Options:
-
-I <mode>stdin mode -
-i <param>string for -Iinput, file for -Ifile, fd for -Ifd -
-O <mode>stdout mode -
-o <param>file for -Ifile, fd for -Ifd -
-E <mode>stderr mode -
-e <param>file for -Efile, fd for -Efd -
-nDryrun mode. Do not execute the generated command. Instead print it to stdout. -
-W <int>Register with L_finally a return trap on stacklevelthat will wait for the popen to finish. Typically -W 0 -
-hPrint this help and return 0.
Arguments:
-
$1variable name to store the result to. -
$@command to execute.
L_proc_get_exitcode
¶
Get exitcode of L_proc.
Option:
-v <var>
Store the output in variable instead of printing it.
Argument:
$1
PID from L_proc_popen
L_proc_get_exitcode_v
¶
L_proc_get_pid
¶
Get PID from L_proc_popen of L_proc.
Option:
-v <var>
Store the output in variable instead of printing it.
Argument:
$1
PID from L_proc_popen
L_proc_get_pid_v
¶
L_proc_get_stdin
¶
Get file descriptor for stdin of L_proc.
Option:
-v <var>
Store the output in variable instead of printing it.
Argument:
$1
PID from L_proc_popen
L_proc_get_stdin_v
¶
L_proc_get_stdout
¶
Get file descriptor for stdout of L_proc.
Option:
-v <var>
Store the output in variable instead of printing it.
Argument:
$1
PID from L_proc_popen
L_proc_get_stdout_v
¶
L_proc_get_stderr
¶
Get file descriptor for stderr of L_proc.
Option:
-v <var>
Store the output in variable instead of printing it.
Argument:
$1
PID from L_proc_popen
L_proc_get_stderr_v
¶
L_proc_get_cmd
¶
Get command of L_proc.
Option:
-v <var>
Store the output in the array variable instead of printing it.
Argument:
$1
PID from L_proc_popen
L_proc_get_cmd_v
¶
L_proc_free
¶
L_proc_popen_finally
¶
Handler that can be closed from L_finally or a signal handler.
Closes file descriptors. If L_SIGNAL is a signal, forwards it to the process. Then wait for the process termination.
Example
L_finally proc -W sleep infinity
Example
L_finally proc sleep infinity
L_finally L_proc_popen_finally "$proc"
# or
L_finally proc sleep infinity
L_finally L_eval 'L_proc_popen_finally "${!1}"' proc
Argument:
$1
L_proc value. This is because the variable may go out of scope.
This is bad. when closing a file descriptor, we "remember" that the file descriptor was closed in the variable. This is less then ideal, but I do not know at this time how to fix it better. Potentially, this can cause unrelated file descriptors to get closed. This might change in the future.
L_proc_printf
¶
Write printf formatted string to coproc.
Arguments:
-
$1PID from L_proc_popen -
$@any printf arguments
L_proc_read
¶
Exec read bultin with -u file descriptor of stdout of coproc.
Arguments:
-
$1PID from L_proc_popen -
$@any builtin read options
L_proc_read_stderr
¶
Exec read bultin with -u file descriptor of stderr of coproc.
Arguments:
-
$1PID from L_proc_popen -
$@any builtin read options
See: L_proc_read
L_proc_close
¶
Close stdin, stdout and stderr of L_proc
Argument:
$1
PID from L_proc_popen
L_proc_close_stdin
¶
Close stdin of L_proc.
Does nothing if already closed or not started with -Opipe.
Argument:
$1
PID from L_proc_popen
L_proc_close_stdout
¶
Close stdout of L_proc.
Does nothing if already closed or not started with -Opipe
Argument:
$1
PID from L_proc_popen
L_proc_close_stderr
¶
Close stderr of L_proc.
Does nothing if already closed or not started with -Epipe.
Argument:
$1
PID from L_proc_popen
L_proc_poll
¶
Check if L_proc is finished.
Argument:
$1
PID from L_proc_popen
Exit: 0 if L_proc is running, 1 otherwise
L_wait
¶
Wait for pids to be finished with a timeout and capture exit codes.
Tries to use waitpid or tail --pid or a busy loop for best performance.
The waiting is uninterruptible by signals.
Note: builtin kill with multiple pids has different exit code depending on posix mode.
Options:
-
-t <timeout>Wait for this long. Timeout 0 results in just collecting all pids. -
-v <var>Exit code of PIDs will be assigned to . The elements of -v and -p arrays are pairs. -
-p <var>PIDs that exited will be assigned to array variable . -
-l <var>Left running PIDs will be assigned to array variable . -
-P <polltime>The time to poll processes when not possible to use waitpid or tail. Default: 0.1 -
-nReturn 0 when at least one of the pids is finished. -
-bBash only. Do not use waitpid or tail. -
-hPrint this help and exit.
Argument:
$@
pids to wait on
Return:
0 on success
2 usage error 124 timeout
L_proc_wait
¶
Wait for L_proc to finish.
If L_proc has already finished execution, will only evaluate -v option.
Options:
-
-t <int>Timeout in seconds. Will try to use waitpid, tail --pid or busy loop with sleep. -
-v <var>Store the L_proc exit code in the variable. -
-cClose L_proc file descriptors before waiting. -
-hPrint this help and return 0.
Argument:
$1
PID from L_proc_popen
Exit:
0 if L_proc has finished
124 if timeout expired
L_read_fds
¶
Read from multiple file descriptors at the same time.
Note
The minimum read -t argument in Bash3.2 is 1 second. It is not possible
to set it lower or to a fraction. This does not work great for Bash3.2 for short timeout, as one read takes at least 1 second to execute.
Example
exec 10< <(for ((i=0;i<5;++i)); do echo $i; sleep 1; done)
exec 11< <(for ((i=0;i<5;++i)); do echo $i; sleep 2; done)
L_read_fds 10 a 11 b
echo "read from 10 fd text: $a"
echo "read from 11 fd text: $b"
Options:
-
-t <timeout>Timeout in seconds. -
-p <timeout>Poll timeout. The read -t argument value. Default: 0.05 or 1 in Bash3.2 -
-hPrint this help and return 0. -
-v <var>After first fd EOF or error, assign the fd number to and return 0. -
-i <var>After first fd EOF or error, assign the argument fd number to and return 0. -
-d <delim>Read -d argument. Default: '' -
-nRun the reading loop possible once.
Arguments:
-
$1File descriptor to read from. -
$2Variable to assign the output of $1. -
$@Continued pairs of file descriptor and variable names.
I do not like this. This should just read from two arrays and return array index.
Return:
0 on success
124 on timeout
L_proc_communicate
¶
Communicate with L_proc.
Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to terminate.
Options:
-
-i <str>Send string to stdin.
Note that if you want to send data to the process’s stdin, you need to create the L_proc object with -I PIPE.
-
-o <var>Append stdout data to this variable. Variable is not cleared.
To get anything, you need to create L_proc object with -O PIPE.
-
-e <var>Append stderr to this variable. Variable is not cleared.
To get anything, you need to create L_proc object with -E PIPE.
-
-t <int>Timeout in seconds. -
-kKill L_proc after communication. -
-v <var>Store the output in variable instead of printing it. -
-hPrint this help and return 0.
Argument:
$1
PID from L_proc_popen
Exit: 0 on success. 2 on usage error. 124 on timeout.
L_proc_send_signal
¶
Send signal to L_proc.
Arguments:
-
$1PID from L_proc_popen -
$2signal to send
L_proc_terminate
¶
Terminate L_proc.
Argument:
$1
PID from L_proc_popen
L_proc_kill
¶
Kill L_proc.
Argument:
$1
PID from L_proc_popen