.. -*- coding: utf-8 -*- .. role:: sref(numref) .. role:: xref(numref) .. Copyright (C) 2019, Wolfgang Scherer, <Wolfgang.Scherer at gmx.de> .. .. This file is part of Development. .. .. Permission is granted to copy, distribute and/or modify this document .. under the terms of the GNU Free Documentation License, Version 1.3 .. or any later version published by the Free Software Foundation; .. with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. .. A copy of the license is included in the section entitled "GNU .. Free Documentation License". .. inline comments (with du_comment_role) .. role:: rem(comment) .. role:: html(raw) :format: html .. role:: shx(code) :language: sh .. highlight:: sh .. rst-class:: narrow xmedium xlarge xhuge xultra ################################################## :rem:`|||:sec:|||`\ Shell basics ################################################## .. >>CODD See `the components of a doctoral dissertation and their order <http://site.uit.no/english/writing-style/bookstructure/>`_ .. >>CODD Dedication .. >>CODD Epigraph .. >>CODD Abstract .. compound:: .. \|:here:| .. >>CODD Introduction .. >>CODD Chapter ================================================== :rem:`|||:sec:|||`\ Job Control ================================================== +----------------------+----------------------------------------------------------------------------------------------------------------------+ | Command | Description | +======================+======================================================================================================================+ | :samp:`jobs` | show jobs (background tasks) | +----------------------+----------------------------------------------------------------------------------------------------------------------+ | :kbd:`C-z` | send signal TSTOP to foreground job, which stops, is sent to the background job queue and becomes the current job | +----------------------+----------------------------------------------------------------------------------------------------------------------+ | :samp:`bg [job no.]` | run current job in job queue in background | +----------------------+----------------------------------------------------------------------------------------------------------------------+ | :samp:`fg [job no.]` | run current job in foreground | +----------------------+----------------------------------------------------------------------------------------------------------------------+ | :samp:`cmd &` | run cmd as background job | +----------------------+----------------------------------------------------------------------------------------------------------------------+ ================================================== :rem:`|||:sec:|||`\ POSIX ================================================== Using POSIX compatible syntax allows shell scripts to run on other systems, where no bash(1) is available (NAS, various embedded systems, old systems with proprietary unixes). So, use `#!/bin/sh` instead of `#!/bin/bash`. Use `The Open Group Base Specifications Issue 7, 2018 edition`_ for general reference. See `sh - shell, the standard command language interpreter`_ and `Shell Command Language`_ for specific shell reference. .. attention:: Be aware that there are very old shells out there that do not conform to POSIX. Mainly because POSIX was not around at their conception. So not everything allowed by POSIX is necessarily failsafe for all shells. ----------------------------------------------------- :rem:`||:sec:||`\ Conventions for Syntax Descriptions ----------------------------------------------------- The conventions for syntax descriptions are covered in `Chapter 12. Utility Conventions`_ of `The Open Group Base Specifications Issue 7, 2018 edition`_. An informal description can also be found in man-pages(7). The syntax for alternative arguments ``{ arg1 | arg2 | arg3 }`` is not part of POSIX, but is mentioned in `syntax - Is there a specification for a man page's SYNOPSIS section? - Stack Overflow`_ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :rem:`|:sec:|`\ Specific Conventions in Templated Shell Scripts ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Single letter options preceded by a single dash ``-`` must not be grouped together. The additional effort is not worth the conceived advantage. Readability is much better, when separating single letter options. In addition to POSIX chapter 12, a shortened ellipses ``..`` may be used in place of a full ellipses ``...``. Clarifying POSIX section 12.8, the vertical bar ``|`` is only used within braces ``{``, ``}`` to indicate exclusive alternatives. Together with brackets ``[``, ``]`` This allows specifying optional syntax variants without an implied order. E.g.: .. code-block:: sh program [ { key=[value] | -key | [!]key } ..] This notation emphasizes that each of these options overrides the effect of a previous occurence. The following syntax description is equivalent, but the exclusive nature of the alternatives is not so clear: .. code-block:: sh program [key=[value]].. [-key].. [[!]key].. This example can be further shortened, if the option description mentions, that the option can be specified multiple times: .. code-block:: sh program [key=[value]] [-key] [[!]key] In addtion to specifying multiple synopsis lines according to POSIX section 12.8, mutually exclusive option may be given summarily as [MODE OPTIONS], which are understood to override any previous mode options. E.g.: .. code-block:: sh program [OPTIONS] [MODE OPTIONS] MODE OPTIONS --one perform action 1 --two perform action 2 .. _`syntax - Is there a specification for a man page's SYNOPSIS section? - Stack Overflow`: https://stackoverflow.com/a/8716112/2127439 .. _`Shell Command Language`: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html .. _`sh - shell, the standard command language interpreter`: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html .. _`The Open Group Base Specifications Issue 7, 2018 edition`: https://pubs.opengroup.org/onlinepubs/9699919799/ .. _`Chapter 12. Utility Conventions`: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html ============================================================================= :rem:`|||:sec:|||`\ Special Purpose Language vs. Generic Programming Language ============================================================================= The bourne shell sh(1) is a special purpose language. It is **not** a generic programming language. Making the shell more like C, with e.g. csh(1), are misguided experiments. Making the excution of the :program:`test` program look like a condition in a programming language is a very special brain dead example of syntactic obfuscation. The standard syntax shows quite clearly, what happens, when the program :program:`test` is executed: .. code-block:: sh if test arg1 arg2 then : fi The alternate program name :program:`[` requires an extra argument `]` for *closing* the fake opening bracket, just so the command execution resembles a *mathematical* condition: .. code-block:: sh if [ arg1 arg2 ] then : fi .. warning:: Using this abomination in a shell scripts results in immediate deletion. Avoid arithmetic expansion :samp:`$(( ... ))`, if :program:`expr`\ (1) can do the job. -------------------------------------------------- :rem:`||:sec:||`\ Variable expansion -------------------------------------------------- To be safe and to make replacemnts simpler, always use curly braces for variable expansion: .. code-block:: sh printf "variable: %s, arg count: %d, args: %s\n" "${variable}" "${#}" "${*}" Emacs support in Shell-script mode: +--------------+-----------+ | Shortcut | Expansion | +==============+===========+ | :kbd:`C-c v` | ${} | +--------------+-----------+ | :kbd:`C-c q` | "${}" | +--------------+-----------+ -------------------------------------------------- :rem:`||:sec:||`\ echo (1) , printf(1) -------------------------------------------------- Do not use echo(1), since it is not portable, use printf(1) instead. Emacs support in Shell-script mode: +------------------+--------------------+ | Shortcut | Expansion | +==================+====================+ | :kbd:`C-c p` | printf "%s\n" | +------------------+--------------------+ | :kbd:`C-u C-c p` | printf >&2 "%s\n" | +------------------+--------------------+ Especially dash(1) (ubuntu system shell) and bash(1) differ extremely. .. code-block:: sh $ ls -l /bin/sh lrwxrwxrwx 1 root root 4 Mai 8 2018 /bin/sh -> dash $ /bin/dash -c 'echo "hello\nnext line"' hello next line $ /bin/dash -c 'echo -e "hello\nnext line"' -e hello next line $ /bin/bash -c 'echo "hello\nnext line"' hello\nnext line $ /bin/bash -c 'echo -e "hello\nnext line"' hello next line Emacs support in Shell-script mode for debug output of variables: :kbd:`arg_count C-c d v v` expands to .. code-block:: sh printf >&2 "# "":DBG: %-${dbg_fwid-15}s: [%s]\n" "arg_count" "${arg_count}" -------------------------------------------------- :rem:`||:sec:||`\ Avoid special bash syntax -------------------------------------------------- Do not use: .. code-block:: sh function func_name { : } but use POSIX compatible syntax instead: .. code-block:: sh func_name () { : } -------------------------------------------------- :rem:`||:sec:||`\ Do not use arrays -------------------------------------------------- Shell arrays are not POSIX compatible! If you think you need to use arrays, you should probably not use the shell but a generic script programming language like awk(1), perl(1) or python(1). See :sref:`sec:WRF loop - single line processing in shell` for single line processing with splitting into fields. See also `bash - How to mark an array in POSIX sh? - Stack Overflow`_ See also `GitHub - krebs/array: a POSIX-compliant implementation of arrays`_, for a POSIX compliant implementation of arrays (untested). .. _`bash - How to mark an array in POSIX sh? - Stack Overflow`: https://stackoverflow.com/questions/6499486/how-to-mark-an-array-in-posix-sh .. _`GitHub - krebs/array: a POSIX-compliant implementation of arrays`: https://github.com/krebs/array .. _`sec:WRF loop - single line processing in shell`: ============================================================== :rem:`|||:sec:|||`\ WRF loop - single line processing in shell ============================================================== Emulating single line processing like sed(1) and awk(1) with :command:`read` in a :command:`while` loop. WRF stands historically for while/read/file. -------------------------------------------------- :rem:`||:sec:||`\ WRF loop -------------------------------------------------- A file is parsed as single lines with the :command:`read` command (see :xref:`lst:WRF loop`, line 14): .. code-block:: sh while read -r in_line See :xref:`fig:WRF loop` for activity diagram. .. _`fig:WRF loop`: .. uml:: _static/wrf_loop-a0.puml :caption: WRF loop .. _`lst:WRF loop`: .. literalinclude:: _static/wrf_loop_x_wrf_loop.sh :caption: WRF loop :emphasize-lines: 12 :language: sh :linenos: -------------------------------------------------- :rem:`||:sec:||`\ WRF loop with standard IFS split -------------------------------------------------- Instead of reading an entire line, the :command:`read` command parses the line into several variables (see :xref:`lst:WRF loop with standard IFS split`, line 14): .. code-block:: sh while read -r in_word0 in_word1 rest The standard IFS is used which splits the line on whitespace. See :xref:`fig:WRF loop with standard IFS split` for activity diagram. .. _`fig:WRF loop with standard IFS split`: .. uml:: _static/wrf_loop-a1.puml :caption: WRF loop with standard IFS split .. _`lst:WRF loop with standard IFS split`: .. literalinclude:: _static/wrf_loop_x_wrf_loop_with_standard_ifs_split.sh :caption: WRF loop with standard IFS split :emphasize-lines: 14 :language: sh :linenos: -------------------------------------------------- :rem:`||:sec:||`\ WRF loop with special IFS split -------------------------------------------------- Instead of reading an entire line, the :command:`read` command parses the line into several variables (see :xref:`lst:WRF loop with special IFS split`, line 14): .. code-block:: sh while IFS=: read -r in_word0 in_word1 rest IFS is set to ``:`` for the :command:`read` command only, which splits the line on a ``:`` character. See :xref:`fig:WRF loop with special IFS split` for activity diagram. .. _`fig:WRF loop with special IFS split`: .. uml:: _static/wrf_loop-a2.puml :caption: WRF loop with special IFS split .. _`lst:WRF loop with special IFS split`: .. literalinclude:: _static/wrf_loop_x_wrf_loop_with_special_ifs_split.sh :caption: WRF loop with special IFS split :emphasize-lines: 14 :language: sh :linenos: ----------------------------------------------------- :rem:`||:sec:||`\ split and process lines with awk(1) ----------------------------------------------------- .. literalinclude:: _static/wrf_loop.sh :caption: AWK script to split and process lines via callback :start-after: .:lst:. awk_script_split_and_process_lines :end-before: .:lst:. awk_script_split_and_process_lines - :language: awk :linenos: .. literalinclude:: _static/wrf_loop.sh :caption: Function split and process lines via callback :start-after: .:lst:. split_and_process_lines :end-before: .:lst:. split_and_process_lines - :language: sh :linenos: .. literalinclude:: _static/wrf_loop.sh :caption: Example for split and process lines via callback :start-after: .:lst:. split_and_process_lines_example :end-before: .:lst:. split_and_process_lines_example - :language: sh :linenos: .. code-block:: text :caption: Script generated by example for split and process lines via callback # -------------------------------------------------- # :DBG: _script : [line=''; col_count=0; col1=; col2=; col3=; col4=; col5=; col6=; col7=; col8=; col9=; col10=; col_process; line='# comment'; col_count=1; col1='# comment'; # ... col_process; line='in the : city'; col_count=2; col1='in the'; col2='city'; # ... col_process; line='and : over : the : mountains'; col_count=4; col1='and'; col2='over'; col3='the'; col4='mountains'; # ... col_process; line=''; col_count=0; # ... col_process; line='som'\''e where : over '\'' the : rainbow'; col_count=3; col1='som'\''e where'; col2='over '\'' the'; col3='rainbow'; # ... col_process; line='some wh'\'''\''ere : over the : rainbow'; col_count=3; col1='some wh'\'''\''ere'; col2='over the'; col3='rainbow'; # ... col_process; line=''; col_count=0; # ... col_process;] .. code-block:: text :caption: Output from example for split and process lines via callback # -------------------------------------------------- # |:WRN:| warning: comment or blank line [] # |:WRN:| warning: comment or blank line [# comment] line: col1 [in the] col2 [city] col3 [] col4 [] col5 [] col1=in the col2=city line: col1 [and] col2 [over] col3 [the] col4 [mountains] col5 [] col1=and col2=over col3=the col4=mountains # |:WRN:| warning: comment or blank line [] line: col1 [som'e where] col2 [over ' the] col3 [rainbow] col4 [] col5 [] col1=som'e where col2=over ' the col3=rainbow line: col1 [some wh''ere] col2 [over the] col3 [rainbow] col4 [] col5 [] col1=some wh''ere col2=over the col3=rainbow # |:WRN:| warning: comment or blank line [] ================================================== :rem:`|||:sec:|||`\ Single quoting ================================================== #. Starting with an unquoted string, assume that it is part of a single quoted string already: .. uml:: :html_format: png :latex_format: png :scale: 100% @startditaa +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | | | | | | | | | | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | e | m | b | e | d | ' | e | d | ' | s | i | n | g | ' | l | e | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ @endditaa #. Find next embedded single quote and insert a single quote before to terminate single quoting ``'`` -> ``''``: .. uml:: :html_format: png :latex_format: png :scale: 100% @startditaa +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | | | | | | ↓ | | | | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | e | m | b | e | d | ' | ' | e | d | ' | s | i | n | g | ' | l | e | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ @endditaa #. Insert a backslash to escape the embedded single quote ``'`` -> ``'\'``: .. uml:: :html_format: png :latex_format: png :scale: 100% @startditaa +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | | | | | | | ↓ | | | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | e | m | b | e | d | ' | \ | ' | e | d | ' | s | i | n | g | ' | l | e | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ @endditaa #. Add a single quote after the escaped single quote to continue quoting ``'`` -> ``'\''``: .. uml:: :html_format: png :latex_format: png :scale: 100% @startditaa +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | | | | | | | | | ↓ | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | e | m | b | e | d | ' | \ | ' | ' | e | d | ' | s | i | n | g | ' | l | e | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ @endditaa #. Repeat steps 2 through 4 for all remaining embedded single quotes ``'`` -> ``'\''``: .. uml:: :html_format: png :latex_format: png :scale: 100% @startditaa +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | | | | | | | | | | | | ↓ | ↓ | | ↓ | | | | | ↓ | ↓ | | ↓ | | | | | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | e | m | b | e | d | ' | \ | ' | ' | e | d | ' | \ | ' | ' | s | i | n | g | ' | \ | ' | ' | l | e | | | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ @endditaa #. Enclose string in single quotes ``'`` ... ``'`` to complete single quoting: .. uml:: :html_format: png :latex_format: png :scale: 100% @startditaa +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | ↓ | | | | | | | | | | | | | | | | | | | | | | | | | | ↓ | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | ' | e | m | b | e | d | ' | \ | ' | ' | e | d | ' | \ | ' | ' | s | i | n | g | ' | \ | ' | ' | l | e | ' | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ @endditaa Activity diagram for algorithm: .. uml:: @startuml start while (for each single quote) is (do) :replace single quote ""'"" with ""'\''""; endwhile :enclose escaped string in single quotes ""'"" ... ""'""; stop @enduml ======================================================================= :rem:`|||:sec:|||`\ Construct correctly quoted shell script ======================================================================= A shell script is assigned to the variable `_script` to be executed for different purposes, e.g. - at a later time: .. code-block:: sh eval "${_script}" - in different shell process, e.g.: .. code-block:: sh sh -c "${_script}" printf "%s\n" "${_script}" | sh - as different user: .. code-block:: sh sudo -u user sh -c "${_script}" - on remote host: .. code-block:: sh ssh user@host "${_script}" printf "%s\n" "${_script}" | ssh user@host -------------------------------------------------- :rem:`||:sec:||`\ Preparations -------------------------------------------------- #. Update snippets to latest version: .. code-block:: sh cd /srv/ftp/pub && ./sync.sh --restore && ./xx-sync-ftp-pub.sh -l 0 #. Create test shell script with template:: snn x_quoted_script.sh #. Expand snippet (at end of line (:kbd:`C-e`) enter key sequence :kbd:`C-x C-e`):: ## (progn (forward-line 1) (snip-insert "sh_f.single-quote" t t "sh" " --key single_quote_minimal") (insert "\n")) #. Add example environment setup in body:: set -- arg1 arg2 'arg with spaces' TEMP_DIR='/tmp/some-rndajom-stuff' #. Add example command:: ( cd "${TEMP_DIR}/" || exit 1; pwd ) for _arg in ${1+"${@}"}; do echo "${_arg}"; done echo 'hello' | cat - #. Execute and study output:: x_quoted_script.sh: 2: cd: cannot cd to /tmp/some-rndajom-stuff/ arg1 arg2 arg with spaces hello -------------------------------------------------- :rem:`||:sec:||`\ Single quoted string -------------------------------------------------- #. Single quote entire command:: _script=' ( cd "${TEMP_DIR}/" || exit 1; pwd ) for _arg in ${1+"${@}"}; do echo "${_arg}"; done echo '\''hello'\'' | cat - ' add some execution tests:: printf "%s\n" "${_script}" printf "%s\n" '--------------------------------------------------' eval "${_script}" printf "%s\n" '--------------------------------------------------' sh -c "${_script}" and observe output:: ( cd "${TEMP_DIR}/" || exit 1; pwd ) for _arg in ${1+"${@}"}; do echo "${_arg}"; done echo 'hello' | cat - -------------------------------------------------- x_quoted_script.sh: 2: cd: cannot cd to /tmp/some-rndajom-stuff/ arg1 arg2 arg with spaces hello -------------------------------------------------- / hello #. Interrupt quoting to insert expanded variables. #. Use :func:`single_quote_enclose` as necessary:: _script=' ( cd '"$( single_quote_enclose "${TEMP_DIR}/" )"' || exit 1; pwd ) for _arg in ${1+"${@}"}; do echo "${_arg}"; done echo '\''hello'\'' | cat ' #. Use :func:`single_quote_args` as necessary:: _script=' ( cd '"$( single_quote_enclose "${TEMP_DIR}/" )"' || exit 1; pwd ) for _arg in '"$( single_quote_args ${1+"${@}"} )"'; do echo "${_arg}"; done echo '\''hello'\'' | cat - ' and observe output:: ( cd '/tmp/some-rndajom-stuff/' || exit 1; pwd ) for _arg in 'arg1' 'arg2' 'arg with spaces'; do echo "${_arg}"; done echo 'hello' | cat - -------------------------------------------------- x_quoted_script.sh: 2: cd: cannot cd to /tmp/some-rndajom-stuff/ arg1 arg2 arg with spaces hello -------------------------------------------------- sh: 2: cd: cannot cd to /tmp/some-rndajom-stuff/ arg1 arg2 arg with spaces hello -------------------------------------------------- :rem:`||:sec:||`\ HERE document -------------------------------------------------- #. Enclose entire command in ``cat <<EOF`` ... ``EOF``, escape as necessary:: cat <<EOF ( cd "${TEMP_DIR}/" || exit 1; pwd ) for _arg in ${1+"${@}"}; do echo "\${_arg}"; done echo 'hello' | cat - EOF and observe output:: ( cd "/tmp/some-rndajom-stuff/" || exit 1; pwd ) for _arg in arg1 arg2 arg with spaces; do echo "${_arg}"; done echo 'hello' | cat - #. Use :func:`single_quote_enclose` and :func:`single_quote_args` as necessary:: cat <<EOF ( cd $( single_quote_enclose "${TEMP_DIR}/" ) || exit 1; pwd ) for _arg in $( single_quote_args ${1+"${@}"} ); do echo "\${_arg}"; done echo 'hello' | cat - EOF and observe output:: ( cd '/tmp/some-rndajom-stuff/' || exit 1; pwd ) for _arg in 'arg1' 'arg2' 'arg with spaces'; do echo "${_arg}"; done echo 'hello' | cat - #. Enclose in subshell expansion ``"$(`` ... ``)"`` for assignment to variable:: _script="$( cat <<EOF ( cd $( single_quote_enclose "${TEMP_DIR}/" ) || exit 1; pwd ) for _arg in $( single_quote_args ${1+"${@}"} ); do echo "\${_arg}"; done echo 'hello' | cat - EOF )" add some execution tests:: printf "%s\n" "${_script}" printf "%s\n" '--------------------------------------------------' eval "${_script}" printf "%s\n" '--------------------------------------------------' sh -c "${_script}" and observe output:: ( cd '/tmp/some-rndajom-stuff/' || exit 1; pwd ) for _arg in 'arg1' 'arg2' 'arg with spaces'; do echo "${_arg}"; done echo 'hello' | cat - -------------------------------------------------- x_quoted_script.sh: 1: cd: cannot cd to /tmp/some-rndajom-stuff/ arg1 arg2 arg with spaces hello -------------------------------------------------- sh: 1: cd: cannot cd to /tmp/some-rndajom-stuff/ arg1 arg2 arg with spaces hello #. Enclose entire command in here document specifying a quoted end-of-file marker ``cat <<'EOF'`` ... ``EOF``, no escaping is necessary: .. code-block:: sh cat <<'EOF' ( cd "${TEMP_DIR}/" || exit 1; pwd ) for _arg in ${1+"${@}"}; do echo "\${_arg}"; done echo 'hello' | cat - EOF and observe output: .. code-block:: text ( cd "${TEMP_DIR}/" || exit 1; pwd ) for _arg in ${1+"${@}"}; do echo "\${_arg}"; done echo 'hello' | cat - The type of quotes (single or double) does not matter. ================================================== :rem:`|||:sec:|||`\ Command execution ================================================== For bash(1), four types of commands are defined: - aliases - shell functions - builtin commands - external programs A POSIX shell like dash(1) does not support aliases. From the man page of bash(1): **COMMAND EXECUTION** After a command has been split into words, if it results in a simple command and an optional list of arguments, the following actions are taken. If the command name contains no slashes, the shell attempts to locate it. [If the shell is interactive or shell option expand_aliases is set and an alias by that name is found, it is expanded.] If there exists a shell function by that name, that function is invoked as described above in FUNCTIONS. If the name does not match a function, the shell searches for it in the list of shell builtins. If a match is found, that builtin is invoked. If the name is neither a shell function nor a builtin, and contains no slashes, bash searches each element of the PATH for a directory containing an executable file by that name. Bash uses a hash table to remember the full pathnames of executable files (see hash under SHELL BUILTIN COMMANDS below). A full search of the directories in PATH is performed only if the command is not found in the hash table. If the search is unsuccessful, the shell searches for a defined shell function named command_not_found_handle. If that function exists, it is invoked with the original command and the original command's arguments as its arguments, and the function's exit status becomes the exit status of the shell. If that function is not defined, the shell prints an error message and returns an exit status of 127. If the search is successful, or if the command name contains one or more slashes, the shell executes the named program in a separate execution environment. Argument 0 is set to the name given, and the remaining arguments to the command are set to the arguments given, if any. If this execution fails because the file is not in executable format, and the file is not a directory, it is assumed to be a shell script, a file containing shell commands. A subshell is spawned to execute it. This subshell reinitializes itself, so that the effect is as if a new shell had been invoked to handle the script, with the exception that the locations of commands remembered by the parent (see hash below under SHELL BUILTIN COMMANDS) are retained by the child. If the program is a file beginning with #!, the remainder of the first line specifies an interpreter for the program. The shell executes the specified interpreter on operating systems that do not handle this executable format themselves. The arguments to the interpreter consist of a single optional argument following the interpreter name on the first line of the program, followed by the name of the program, followed by the command arguments, if any. .. note:: **DO NOT** set the shell option expand_aliases in scripts. Generally, **DO NOT** write bash(1) scripts. Stick to **POSIX**. :xref:`fig:Shell command execution process` shows an activity diagram for the command execution process. .. _`fig:Shell command execution process`: .. uml:: :caption: Shell command execution process @startuml partition "Shell command execution process" { start if (command is a simple command (no slashes)?) then (yes) if ((shell is interactive or shell\n option expand_aliases is set)\nand an alias by this name is defined?) then (yes) :expand alias; elseif (a //shell function//\nby this name exists?) then (yes) :execute //shell function//; elseif (a //builtin command//\nby this name exist?) then (yes) :execute //builtin command//; else while (for path_element in PATH) is (do) if (//path_element/command// exists?) then (yes) :execute //path_element/command//; break endif endwhile endif elseif (//command// file exists?) then (yes) :execute //command// file; else :error; end endif stop } @enduml ================================================== :rem:`|||:sec:|||`\ **.** command ================================================== The **.** command is an include mechanism for script files (much like the preprocessor command `#include` in C). Note, that the standard definition of **.** ignores all arguments, which means, that no arguments are allowed to avoid inconsistent behavior for different shells.. All variable assignments in the included file are incorporated into the shell environment. From man page of bash(1): . filename [...] Read and execute commands from filename in the current shell environment and return the exit status of the last command executed from filename. If filename does not contain a slash, filenames in PATH are used to find the directory containing filename. The file searched for in PATH need not be executable. When bash is not in posix mode, the current directory is searched if no file is found in PATH. If the sourcepath option to the shopt builtin command is turned off, the PATH is not searched. [...] The return status is the status of the last command exited within the script (0 if no commands are executed), and false if filename is not found or cannot be read. ================================================== :rem:`|||:sec:|||`\ Subshell and compound commands ================================================== From man page of bash(1): Compound Commands A compound command is one of the following. In most cases a list in a command's description may be separated from the rest of the command by one or more newlines, and may be followed by a newline in place of a semicolon. (list) list is executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below). Variable assignments and builtin commands that affect the shell's environment do not remain in effect after the command completes. The return status is the exit status of list. { list; } list is simply executed in the current shell environment. list must be terminated with a newline or semicolon. This is known as a group command. The return status is the exit status of list. Note that unlike the metacharacters ( and ), { and } are reserved words and must occur where a reserved word is permitted to be recognized. Since they do not cause a word break, they must be separated from list by whitespace or another shell metacharacter. Builtin commands in a subshell do not affect the shell environment in the parent shell, e.g.: .. code-block:: sh VAR='value' ( VAR='something'; echo "${VAR}"; ) echo "${VAR}"; results in output of: .. code-block:: text something value Builtin commands in a group command do affect the shell environment outside the group, e.g.: .. code-block:: sh VAR='value' { VAR='something'; echo "${VAR}"; } echo "${VAR}"; results in output of: .. code-block:: text something something .. note:: A command in a pipeline is implicitely executed in a subshell. I.e.: .. code-block:: sh var=outer echo world | { var=inner; echo hello; cat - } echo "${var}" is equivalent to: .. code-block:: sh var=outer echo world | ( var=inner; echo hello; cat - ) echo "${var}" .. note:: Generally avoid :samp:`{{ list }}` grouping. Especially the side effect of shell environment manipulation. .. note:: A subshell is not equivalent to execution of an external shell script. A subshell can access all variables of the parent shell environment, whether they are exported or not. E.g.: .. code-block:: sh unexported='internal value' export exported='external value' ( echo "[${unexported}]"; echo "[${exported}]" ) results in output .. code-block:: text [internal value] [external value] Whereas an external shell script can only access exported variables of the parent shell environment. E.g.: .. code-block:: sh unexported='internal value' export exported='external value' cat <<'EOF' | sh echo "[${unexported}]"; echo "[${exported}]" EOF results in output .. code-block:: text [] [external value] .. >>CODD Conclusion .. >>CODD Appendix A .. \|:here:| .. >>CODD Notes .. ================================================== .. :rem:`|||:sec:|||`\ Footnotes .. ================================================== :html:`<hr>` .. \[#] .. >>CODD Reference List/Bibliography .. ================================================== .. :rem:`|||:sec:|||`\ References .. ================================================== .. include:: doc_defs.inc .. include:: doc_defs_combined.inc .. .. \||<-snap->|| doc_standalone .. include:: doc/doc_defs_secret.inc .. \||<-snap->|| doc_standalone .. \||<-snap->|| not_doc_standalone .. include:: doc_defs_secret.inc .. \||<-snap->|| not_doc_standalone .. _`Wolfgang Scherer`: wolfgang.scherer@gmx.de