logo

drewdevault.com

[mirror] blog and personal website of Drew DeVault git clone https://hacktivis.me/git/mirror/drewdevault.com.git

Introduction-to-POSIX-shell.md (5563B)


  1. ---
  2. date: 2018-02-05
  3. layout: post
  4. title: Introduction to POSIX shell
  5. tags: [shell, instructional]
  6. ---
  7. What the heck is the POSIX shell anyway? Well, the POSIX (the Portable Operating
  8. System Interface) shell is the standard Unix shell - standard meaning it was
  9. formally defined and shipped in a published standard. This makes shell scripts
  10. written for it portable, something no other shell can lay claim to. The POSIX
  11. shell is basically a formalized version of the venerable Bourne shell, and on
  12. your system it lives at `/bin/sh`, unless you're one of the unlucky masses for
  13. whom this is a symlink to bash.
  14. ## Why use POSIX shell?
  15. The "Bourne Again shell", aka bash, is not standardized. Its grammar,
  16. features, and behavior aren't formally written up anywhere, and only one
  17. implementation of bash exists. Without a standard, bash is defined *by* its
  18. implementation. POSIX shell, on the other hand, has many competing
  19. implementations on many different operating systems - all of which are
  20. compatible with each other because they conform to the standard.
  21. Any shell that utilizes features specific to Bash are not portable, which means
  22. you cannot take them with you to any other system. Many Linux-based systems do
  23. not use Bash or GNU coreutils. Outside of Linux, pretty much everyone but Hurd
  24. does *not* ship GNU tools, including bash[^1]. On any of these systems, scripts
  25. using "bashisms" will not work.
  26. This is bad if your users wish to utilize your software anywhere other than
  27. GNU/Linux. If your build tooling utilizes bashisms, your software will not build
  28. on anything but GNU/Linux. If you ship runtime scripts that use bashisms, your
  29. software will not *run* on anything but GNU/Linux. The case for sticking to
  30. POSIX shell in shipping software is compelling, but I argue that you should
  31. stick to POSIX shell for your personal scripts, too. You might not care now, but
  32. when you feel like flirting with other Unicies you'll thank me when all of your
  33. scripts work.
  34. One place where POSIX shell does *not* shine is for interactive use - a place
  35. where I think bash sucks, too. Any shell you want to use for your day-to-day
  36. command line work is okay in my book. I use fish. Use whatever you like
  37. interactively, but stick to POSIX sh for your scripts.
  38. ## How do I use POSIX shell?
  39. At the top of your scripts, put `#!/bin/sh`. You don't have to worry about using
  40. `env` here like you might have been trained to do with bash: `/bin/sh` is the
  41. standardized location for the POSIX shell, and any standards-conforming system
  42. will either put it there or make your script work anyway.[^2]
  43. The next step is to avoid bashisms. There are many, but here are a few that
  44. might trip you up:
  45. - `[[ condition ]]` does not work; use `[ condition ]`
  46. - Arrays do not work; [use IFS](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05)
  47. - Local variables do not work; use a subshell
  48. The easiest way to learn about POSIX shell is to [read the
  49. standard](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html) -
  50. it's not too dry and shorter than you think.
  51. ## Using standard coreutils
  52. The last step to writing portable scripts is to use portable tools. Your system
  53. may have GNU coreutils installed, which provides tools like `grep` and `cut`.
  54. Unfortunately, GNU has extended these tools with its own non-portable flags and
  55. tools. It's important that you avoid these.
  56. One dead giveaway of a non-portable flag is long flags, e.g. `grep --file=FILE`
  57. as opposed to `grep -f`. The POSIX standard only defines the `getopt` function -
  58. not the proprietary GNU `getopt_long` function that's used to interpret long
  59. options. As a result, no long flags are standardized. You might worry that this
  60. will make your scripts difficult to understand, but I think that on the whole it
  61. will not. Shell scripts are already pretty alien and require some knowledge to
  62. understand. Is knowledge of what the magic word `grep` means much different
  63. from knowledge of what `grep -E` means?
  64. I also like that short flags allow you to make more concise command lines. Which
  65. is better: `ps --all --format=user --without-tty`, or `ps -aux`? If you are
  66. inclined to think the former, do you also prefer `function(a, b, c) { return a +
  67. b + c; }` over `(a, b, c) => a + b + c`? Conciseness matters, and POSIX shell
  68. supports comments if necessary!
  69. Some tips for using short flags:
  70. - They can be collapsed: `cmd -a -b -c` is equivalent to `cmd -abc`
  71. - If they take additional arguments, either a space or no separation is
  72. acceptable: `cmd -f"hello world"` or `cmd -f "hello world"`
  73. A good reference for learning about standardized commands is, once again, [the
  74. standard](http://pubs.opengroup.org/onlinepubs/9699919799/). From this page,
  75. search for the command you want, or navigate through "Shell & Utilities" ->
  76. "Utilities" for a list. If you have `man-pages` installed, you will also find
  77. POSIX man pages installed on your system with the `p` postfix, such as `man 1p
  78. grep`. Note: at the time of writing, the POSIX man pages do not use dashes if
  79. your locale is UTF-8, which makes searching for flags with `/` difficult. Use
  80. `env LC_ALL=POSIX man 1p grep` if you need to search for flags, and I'll speak
  81. to the maintainer of man-pages about this.
  82. [^1]: A reader points out that macOS ships an ancient version of bash.
  83. [^2]: *2018-05-15 correction*: `#!/bin/sh` is unfortunately not standardized by POSIX. However, I still recommend its use, as most operating systems will place it there. The portable way to invoke shell scripts is `sh path/to/script`.