logo

drewdevault.com

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

Shell-literacy.md (6752B)


  1. ---
  2. date: 2020-12-12
  3. title: Become shell literate
  4. outputs: [html, gemtext]
  5. ---
  6. Shell literacy is one of the most important skills you ought to possess as a
  7. programmer. The Unix shell is one of the most powerful ideas ever put to code,
  8. and should be second nature to you as a programmer. No other tool is nearly as
  9. effective at commanding your computer to perform complex tasks quickly —
  10. or at storing them as scripts you can use later.
  11. In my workflow, I use Vim as my editor, and Unix as my "IDE". I don't trick out
  12. [my vimrc](https://git.sr.ht/~sircmpwn/dotfiles/tree/master/.vimrc) to add a
  13. bunch of IDE-like features — the most substantial plugin I use on a daily
  14. basis is [Ctrl+P](https://github.com/ctrlpvim/ctrlp.vim), and that just makes it
  15. easier to open files. Being Vim literate is a valuable skill, but an important
  16. detail is knowing when to drop it. My daily workflow involves several open
  17. terminals, generally one with Vim, another to run builds or daemons, and a third
  18. which just keeps a shell handy for anything I might ask of it.
  19. [![Screenshot of my workspace](https://l.sr.ht/g_oL.png)](https://l.sr.ht/g_oL.png)
  20. The shell I keep open allows me to perform complex tasks and answer complex
  21. questions as I work. I find interesting things with [git grep][git grep],
  22. perform bulk find-and-replace with [sed][sed], answer questions with
  23. [awk][awk], and perform more intricate tasks on-demand with ad-hoc shell
  24. commands and pipelines. I have the freedom to creatively solve problems without
  25. being constrained to the rails laid by IDE designers.
  26. [git grep]: https://git-scm.com/docs/git-grep
  27. [sed]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html#top
  28. [awk]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/awk.html#top
  29. Here's an example of a problem I encountered recently: I had a bunch of changes
  30. in a git repository. I wanted to restore deleted files without dropping the rest
  31. of my changes, but there were hundreds of these. How can I efficiently address
  32. this problem?
  33. Well, I start by getting a grasp of the scale of the issue with git status,
  34. which shows hundreds of deleted files that need to be restored. This scale is
  35. beyond the practical limit of manual intervention, so I switch to git status
  36. -s to get a more pipeline-friendly output.
  37. ```
  38. $ git status -s
  39. D main/a52dec/APKBUILD
  40. D main/a52dec/a52dec-0.7.4-build.patch
  41. D main/a52dec/automake.patch
  42. D main/a52dec/fix-globals-test-x86-pie.patch
  43. D main/aaudit/APKBUILD
  44. D main/aaudit/aaudit
  45. D main/aaudit/aaudit-common.lua
  46. D main/aaudit/aaudit-repo
  47. D main/aaudit/aaudit-server.json
  48. D main/aaudit/aaudit-server.lua
  49. ...
  50. ```
  51. I can work with this. I add grep \'^ D\' to filter out any entries which were
  52. not deleted, and pipe it through awk \'{ print $2 }\' to extract just the
  53. filenames. I'll often run the incomplete pipeline just to check my work and
  54. catch my bearings:
  55. ```
  56. $ git status -s | grep '^ D' | awk '{ print $2 }'
  57. main/a52dec/APKBUILD
  58. main/a52dec/a52dec-0.7.4-build.patch
  59. main/a52dec/automake.patch
  60. main/a52dec/fix-globals-test-x86-pie.patch
  61. main/aaudit/APKBUILD
  62. main/aaudit/aaudit
  63. main/aaudit/aaudit-common.lua
  64. main/aaudit/aaudit-repo
  65. main/aaudit/aaudit-server.json
  66. main/aaudit/aaudit-server.lua
  67. ...
  68. ```
  69. Very good — we have produced a list of files which we need to address.
  70. Note that, in retrospect, I could have dropped the grep and just used awk to the
  71. same effect:
  72. ```
  73. $ git status -s | awk '/^ D/ { print $2 }'
  74. main/a52dec/APKBUILD
  75. main/a52dec/a52dec-0.7.4-build.patch
  76. main/a52dec/automake.patch
  77. main/a52dec/fix-globals-test-x86-pie.patch
  78. main/aaudit/APKBUILD
  79. main/aaudit/aaudit
  80. main/aaudit/aaudit-common.lua
  81. main/aaudit/aaudit-repo
  82. main/aaudit/aaudit-server.json
  83. main/aaudit/aaudit-server.lua
  84. ...
  85. ```
  86. However, we're just writing an ad-hoc command here to solve a specific,
  87. temporary problem — finesse is not important. This command isn't going to
  88. be subjected to a code review. Often my thinking in these situations is to solve
  89. one problem at a time: "filter the list" and "reword the list". Anyway, the
  90. last step is to actually use this list of files to address the issue, with the
  91. help of [xargs][xargs].
  92. [xargs]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/xargs.html#top
  93. ```
  94. $ git status -s | awk '/^ D/ { print $2 }' | xargs git checkout --
  95. ```
  96. Let's look at some more examples of interesting ad-hoc shell pipelines.
  97. Naturally, I wrote a shell pipeline to find some:
  98. ```
  99. $ history | cut -d' ' -f2- | awk -F'|' '{ print NF-1 " " $0 }' | sort -n | tail
  100. ```
  101. Here's the breakdown:
  102. - `history` prints a list of my historical shell commands.
  103. - `cut -d' ' -f2-` removes the first field from each line, using space as a
  104. delimiter. `history` numbers every command, and this removes the number.
  105. - `awk -F'|' '{ print NF-1 " " $0 }` tells awk to use | as the field delimiter
  106. for each line, and print each line prefixed with the number of fields. This
  107. prints every line of my history, prefixed with the number of times the pipe
  108. operator appears in that line.
  109. - `sort -n` numerically sorts this list.
  110. - `tail` prints the last 10 items.
  111. This command, written in the moment, finds, characterizes, filters, and sorts my
  112. shell history by command complexity. Here are a couple of the cool shell
  113. commands I found:
  114. Play the 50 newest videos in a directory with
  115. [mpv](https://github.com/mpv-player/mpv):
  116. ```
  117. ls -tc | head -n50 | tr '\n' '\0' | xargs -0 mpv
  118. ```
  119. I use this command all the time. If I want to watch a video later, I will
  120. [touch][touch] the file so it appears at the top of this list. Another command
  121. transmits a tarball of a patched version of [Celeste][celeste] to a friend using
  122. netcat, minus the (large) game assets, with a progress display via [pv][pv]:
  123. [touch]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/touch.html#top
  124. [Celeste]: http://www.celestegame.com/
  125. [pv]: http://www.ivarch.com/programs/pv.shtml
  126. ```
  127. find . ! -path './Content/*' | xargs tar -cv | pv | zstd | nc 204:fbf5:... 12345
  128. ```
  129. And on my friend's end:
  130. ```
  131. nc -vll :: 12345 | zstdcat | pv | tar -xv
  132. ```
  133. tar, by the way, is an under-rated tool for moving multiple files through a
  134. pipeline. It can read and write tarballs to stdin and stdout!
  135. I hope that this has given you a tantalizing taste of the power of the Unix
  136. shell. If you want to learn more about the shell, I can recommend
  137. [shellhaters.org](http://shellhaters.org/) as a great jumping-off point into
  138. various shell-related parts of the POSIX specification. Don't be afraid of the
  139. spec — it's concise, comprehensive, comprehensible, and full of examples.
  140. I would also *definitely* recommend taking some time to learn awk in particular:
  141. [here's a brief tutorial](https://ferd.ca/awk-in-20-minutes.html).