# file : libbutl/manifest-parser.bash.in # license : MIT; see accompanying LICENSE file if [ "$butl_manifest_parser" ]; then return 0 else butl_manifest_parser=true fi @import libbutl.bash/utility@ # Parse the manifest reading from stdin and writing the binary representation # to stdout. # # Normally you would use the start/finish functions below. But if you don't # care about errors, the following would be the typical usage: # # while IFS=: read -r -d '' n v; do # ... # done < <(butl_parse_manifest) # function butl_parse_manifest () { "$(butl_path)/manifest" parse } # Start the manifest parsing co-process setting the following "return" # variables: # # butl_manifest_parser_ofd # butl_manifest_parser_ifd # butl_manifest_parser_pid # # If is not specified, then read from stdin. # # The typical usage: # # butl_manifest_parser_start # # while IFS=: read -ru "$butl_manifest_parser_ofd" -d '' n v; do # ... # done # # butl_manifest_parser_finish # function butl_manifest_parser_start () # [] { if [ "$#" -gt 0 ]; then exec {butl_manifest_parser_ifd}<"$1" else exec {butl_manifest_parser_ifd}<&0 fi # Note that bash co-process facility is racy: as soon as the process # finishes, bash unsets COPROC/COPROC_PID (my guess would be it checks after # each command in the script). This specific sequence of steps (and the one # in *_finish()) seems to work reliably at least from bash 4.3.30 and # up. See the following resources for details: # # http://wiki.bash-hackers.org/syntax/keywords/coproc # http://tldp.org/LDP/abs/html/bashver4.html (coproc section) # https://lists.gnu.org/archive/html/bug-bash/2014-02/msg00017.html # https://lists.gnu.org/archive/html/bug-bash/2012-12/msg00069.html # https://lists.gnu.org/archive/html/bug-bash/2012-10/msg00027.html # # Also note that the bash bug (still present in 5.0) prevents running # multiple coprocesses at a time (see the BUGS section of bash(1) man page # for details). # # An update: it turns out that we still can end up with an unset COPROC if # the process finishes too early. To avoid that we suspend the subshell # before executing the parser process and resume it after querying the # COPROC value. Note that we need to be careful not to attempt to resume the # process that hasn't suspended itself (see butl_resume_process() for # details). # # Note that the suspend builtin doesn't work in subshells by default since # there is no job control enabled for them. Also when it is enabled (via # `set -m`), the builtin stops subshells recursively up to the command being # run from the interactive shell, which is not what we want. That's why we # use kill which is also a builtin and thus presumably is not slower than # suspend. # coproc { kill -SIGSTOP $BASHPID; exec "$(butl_path)/manifest" parse; } \ <&"$butl_manifest_parser_ifd" exec {butl_manifest_parser_ofd}<&"${COPROC[0]}" butl_manifest_parser_pid="$COPROC_PID" butl_resume_process "$butl_manifest_parser_pid" } # Finish the manifest parsing co-process. # function butl_manifest_parser_finish () { exec {butl_manifest_parser_ofd}<&- wait "$butl_manifest_parser_pid" exec {butl_manifest_parser_ifd}<&- }