<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jonathan Bowman</title>
    <description>The latest articles on DEV Community by Jonathan Bowman (@bowmanjd).</description>
    <link>https://dev.to/bowmanjd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F308244%2Ff2d68952-d239-4106-89a4-84f632c13f81.png</url>
      <title>DEV Community: Jonathan Bowman</title>
      <link>https://dev.to/bowmanjd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bowmanjd"/>
    <language>en</language>
    <item>
      <title>Linux on the Dell XPS: Fixing AX201 Wi-Fi performance</title>
      <dc:creator>Jonathan Bowman</dc:creator>
      <pubDate>Wed, 06 Apr 2022 11:03:39 +0000</pubDate>
      <link>https://dev.to/bowmanjd/linux-on-the-dell-xps-fixing-ax201-wi-fi-performance-2al5</link>
      <guid>https://dev.to/bowmanjd/linux-on-the-dell-xps-fixing-ax201-wi-fi-performance-2al5</guid>
      <description>&lt;p&gt;I am very happy with Linux on my Dell XPS 13 9310. I use the latest version of Fedora (35 at the time of this writing, soon to be upgraded to 36).&lt;/p&gt;

&lt;p&gt;However, the Wi-Fi connection gave me great difficulty for months before I learned that performance is much better with the power saving functionality turned off. With power saving on, I would often lose packets right and left. This was a typical ping session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[bowmanjd@lappy386 ~]$ ping 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=3 ttl=64 time=223 ms
64 bytes from 192.168.0.1: icmp_seq=4 ttl=64 time=1.15 ms
64 bytes from 192.168.0.1: icmp_seq=5 ttl=64 time=15.3 ms
64 bytes from 192.168.0.1: icmp_seq=6 ttl=64 time=1.56 ms
^C
--- 192.168.0.1 ping statistics ---
6 packets transmitted, 4 received, 33.3333% packet loss, time 5075ms
rtt min/avg/max/mdev = 1.147/60.182/222.714/94.010 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the first two dropped packets. Sometimes more, sometimes less. At first, I blamed our wireless access point, yet no other client device had this problem. Finally, I booted into Windows on the XPS and noticed much better wireless performance. Clearly, this is a driver issue of some sort.&lt;/p&gt;

&lt;p&gt;There may be a variety of solutions for this, and I suspect this will one day be fixed in the kernel, but here is how I solved it for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The NetworkManager solution
&lt;/h2&gt;

&lt;p&gt;For Linux distros using NetworkManager, add a new configuration file to the &lt;code&gt;/etc/NetworkManager/conf.d/&lt;/code&gt; directory. Name it something like &lt;code&gt;disable_power_save.conf&lt;/code&gt; with the contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[connection]&lt;/span&gt;
&lt;span class="py"&gt;wifi.powersave&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Elevate to root first using &lt;code&gt;sudo&lt;/code&gt; or similar.&lt;/p&gt;

&lt;p&gt;One command can do it all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"[connection]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;wifi.powersave = 2&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/NetworkManager/conf.d/disable_power_save.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restart NetworkManager with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart NetworkManager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the performance with ping, and there should be a marked improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  wifi.powersave config options
&lt;/h2&gt;

&lt;p&gt;NetworkManager has several configurable options for wireless adapter settings, including &lt;code&gt;powersave&lt;/code&gt;. You can &lt;a href="https://networkmanager.dev/docs/api/latest/settings-802-11-wireless.html"&gt;read about them in the API documentation&lt;/a&gt;. There, we discover the following &lt;code&gt;powersave&lt;/code&gt; options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NM_SETTING_WIRELESS_POWERSAVE_DEFAULT (0) (use the globally configured value). &lt;/li&gt;
&lt;li&gt;NM_SETTING_WIRELESS_POWERSAVE_IGNORE (1) (don't touch currently configured setting)&lt;/li&gt;
&lt;li&gt;NM_SETTING_WIRELESS_POWERSAVE_DISABLE (2) (disable Wi-Fi power saving)&lt;/li&gt;
&lt;li&gt;NM_SETTING_WIRELESS_POWERSAVE_ENABLE (3) (enable Wi-Fi power saving)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, I want to disable Wi-Fi power saving, thus the value of 2 in the config file, set with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[connection]&lt;/span&gt;
&lt;span class="py"&gt;wifi.powersave&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Detecting and setting the power-save setting on the wireless card
&lt;/h2&gt;

&lt;p&gt;With or without NetworkManager, the power save setting of the AX201 and other Wi-Fi cards can be read with the &lt;code&gt;iw&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;First, find the name of your wireless adapter with &lt;code&gt;iw dev&lt;/code&gt; or to get just the name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iw dev | grep -o 'Interface.*'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The wireless adapter likely starts with "wl". Mine is &lt;code&gt;wlp0s20f3&lt;/code&gt;, so the following works to get the current powersave setting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iw dev wlp0s20f3 get power_save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have already made the NetworkManager config changes and restarted NetworkManager, then the result should be &lt;code&gt;Power save: off&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A similar command can be used to turn power_save back on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iw dev wlp0s20f3 set power_save on
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Awaiting other solutions...
&lt;/h2&gt;

&lt;p&gt;Of course, I hope that a driver solution comes soon with new kernel releases. Likely the above solution will not be necessary long term. If you have encountered other ways to solve this problem, please feel free to use the comments!&lt;/p&gt;

&lt;h2&gt;
  
  
  Other resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://gist.github.com/jcberthon/ea8cfe278998968ba7c5a95344bc8b55"&gt;Jean-Christophe Berthon's gist&lt;/a&gt; with NetworkManager scripts and notes&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bbs.archlinux.org/viewtopic.php?pid=1690406#p1690406"&gt;javamarket's comments in this archlinux forum topic&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
    </item>
    <item>
      <title>How to Upgrade to Fedora 37 In Place on Windows Subsystem for Linux (WSL)</title>
      <dc:creator>Jonathan Bowman</dc:creator>
      <pubDate>Sun, 18 Apr 2021 21:42:29 +0000</pubDate>
      <link>https://dev.to/bowmanjd/how-to-upgrade-fedora-in-place-on-windows-subsystem-for-linux-wsl-oh3</link>
      <guid>https://dev.to/bowmanjd/how-to-upgrade-fedora-in-place-on-windows-subsystem-for-linux-wsl-oh3</guid>
      <description>&lt;p&gt;The time has once again arrived to upgrade Fedora. As &lt;a href="https://dev.to/bowmanjd/install-fedora-on-windows-subsystem-for-linux-wsl-4b26"&gt;detailed in another article&lt;/a&gt;, I installed Fedora on WSL 2. Now I want to upgrade to Fedora version 37. I could do a clean install of course, using the steps detailed in that article, but I want to upgrade in place.&lt;/p&gt;

&lt;p&gt;How do we do that?&lt;/p&gt;

&lt;p&gt;Here are the steps I use, based on &lt;a href="https://docs.fedoraproject.org/en-US/quick-docs/dnf-system-upgrade/" rel="noopener noreferrer"&gt;official Fedora instructions&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backup first
&lt;/h2&gt;

&lt;p&gt;Just in case, yes?&lt;/p&gt;

&lt;p&gt;First, clean up downloaded packages, etc. within Fedora:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;sudo dnf clean all
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, exit WSL and export the whole installation to a tarball (this assumes your distro name is "fedora"):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fedora&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;\Downloads\fedora-wsl.tar&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may want a different folder than &lt;code&gt;Downloads&lt;/code&gt;; specify the location you desire.&lt;/p&gt;

&lt;p&gt;Depending on what packages you installed, it may be as small as a quarter GB, or it could be far larger. You could gzip it if you want the storage size to be even smaller. Next time you want to start fresh, you can do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;\wsl\freshfedora&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;freshfedora&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;\wsl\freshfedora&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;\Downloads\fedora-wsl.tar&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Begin the upgrade: freshen up
&lt;/h2&gt;

&lt;p&gt;It is important to have a refreshed package index, and upgrade all packages to the latest. You can do so with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;sudo dnf upgrade --refresh
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install the upgrade system
&lt;/h2&gt;

&lt;p&gt;We will need &lt;a href="https://docs.fedoraproject.org/en-US/quick-docs/dnf-system-upgrade/" rel="noopener noreferrer"&gt;DNF System Upgrade&lt;/a&gt; in order to make the leap, so let's install it now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;sudo dnf install dnf-plugin-system-upgrade
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Download new release packages
&lt;/h2&gt;

&lt;p&gt;To prepare and download for a system upgrade to Fedora 37, the following should do the trick:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;sudo dnf system-upgrade download --releasever=37
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will take a bit of time.&lt;/p&gt;

&lt;p&gt;Yes, you should feel fine about importing the new GPG for Fedora 37, so you can answer "y" to that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reboot?
&lt;/h2&gt;

&lt;p&gt;If using WSL, this is where it gets a little strange, but only a little.&lt;/p&gt;

&lt;p&gt;I first set a flag to indicate reboots are not necessary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;export DNF_SYSTEM_UPGRADE_NO_REBOOT=1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not sure how necessary that is, but it doesn't hurt. We can restart WSL ourselves, if we need to, but I have not found that to be required. If you are doing this on a full Fedora system (not WSL), please do not set this flag; you need to do an actual reboot.&lt;/p&gt;

&lt;p&gt;Now we trigger the update, strangely, with the upgrade and reboot command (pass the &lt;code&gt;-E&lt;/code&gt; flag to &lt;code&gt;sudo&lt;/code&gt; in order to utilize the &lt;code&gt;DNF_SYSTEM_UPGRADE_NO_REBOOT&lt;/code&gt; variable defined earlier):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;sudo -E dnf system-upgrade reboot
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;Reboot turned off, not rebooting.&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Moment of truth: launch the upgrade
&lt;/h2&gt;

&lt;p&gt;Now trigger the actual upgrade with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;sudo -E dnf system-upgrade upgrade
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finalization
&lt;/h2&gt;

&lt;p&gt;You should now have a fresh Fedora 37 system.&lt;/p&gt;

&lt;p&gt;In case you upgraded from Fedora 32 or earlier, the RPM database backend has changed somewhat. Refresh it with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;sudo rpmdb --rebuilddb
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, this should not be necessary unless you were on a much earlier version of Fedora.&lt;/p&gt;

&lt;p&gt;Then refresh and upgrade all the packages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;sudo dnf upgrade --refresh
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just to be as clean as possible (you may still have processes running that are actually older versions than installed), why not restart your WSL distro. Open a Powershell window, and terminate your WSL instance. Assuming your instance is named "fedora" you can &lt;code&gt;wsl -t fedora&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, relaunch with &lt;code&gt;wsl -d fedora&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference: all the steps in one place
&lt;/h2&gt;

&lt;p&gt;For your reference, here are all the steps in one block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;sudo dnf upgrade --refresh
sudo dnf install dnf-plugin-system-upgrade
sudo dnf system-upgrade download --releasever=37
export DNF_SYSTEM_UPGRADE_NO_REBOOT=1
sudo -E dnf system-upgrade reboot
sudo -E dnf system-upgrade upgrade
sudo dnf upgrade --refresh
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enjoy your shiny new Fedora!&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep upgrading
&lt;/h2&gt;

&lt;p&gt;If you chose to upgrade to a beta or prerelease, there should be no need to reinstall. &lt;a href="https://fedoraproject.org/wiki/Upgrading_from_pre-release_to_final#Performing_the_upgrade" rel="noopener noreferrer"&gt;Just keep upgrading&lt;/a&gt;, as often as you like; the process is pretty seamless:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;sudo dnf upgrade
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you daringly chose to use Fedora 37, for instance, upgrade as often as you like with the above command, and you will eventually (by the end of October 2022) be at release.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>fedora</category>
      <category>wsl</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Two Methods for Testing HTTPS API Calls with Python and pytest and Also Communicating with the in-Laws</title>
      <dc:creator>Jonathan Bowman</dc:creator>
      <pubDate>Tue, 16 Mar 2021 23:19:32 +0000</pubDate>
      <link>https://dev.to/bowmanjd/two-methods-for-testing-https-api-calls-with-python-and-pytest-and-also-communicating-with-the-in-laws-388n</link>
      <guid>https://dev.to/bowmanjd/two-methods-for-testing-https-api-calls-with-python-and-pytest-and-also-communicating-with-the-in-laws-388n</guid>
      <description>&lt;p&gt;API endpoints and web URLs are, thankfully, more secure than ever, usually requiring encrypted HTTPS. &lt;a href="https://python.org" rel="noopener noreferrer"&gt;Python&lt;/a&gt; works well as an HTTPS client, and &lt;a href="https://docs.pytest.org/" rel="noopener noreferrer"&gt;pytest&lt;/a&gt; simplifies testing Python-based tools or libraries. Tools like &lt;a href="https://vcrpy.readthedocs.io/" rel="noopener noreferrer"&gt;VCR.py&lt;/a&gt; or the combination of &lt;a href="https://pytest-httpserver.readthedocs.io/" rel="noopener noreferrer"&gt;pytest-httpserver&lt;/a&gt; and &lt;a href="https://trustme.readthedocs.io/" rel="noopener noreferrer"&gt;trustme&lt;/a&gt; provide an additional testing layer that is fast and convenient, and well-suited for HTTPS work. This will help with family gatherings. Let me show you.&lt;/p&gt;

&lt;h2&gt;
  
  
  A potentially awkward social situation, solved by HTTPS
&lt;/h2&gt;

&lt;p&gt;Imagine this scenario: you recently married into a wonderful family, but your spouse's parents only speak &lt;a href="https://en.wikipedia.org/wiki/Pig_Latin" rel="noopener noreferrer"&gt;pig Latin&lt;/a&gt;. Unfortunately, you are not fluent in this exotic language, and you are worried about the next family gathering. As you can guess, this is a common situation for many, so, thankfully, &lt;a href="https://api.funtranslations.com/translate/piglatin.json?text=Thank+you+for+your+hospitality+I+brought+a+turkey" rel="noopener noreferrer"&gt;there is an API for that&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Being the enterprising Python developer that you are, you write a Python client to serve as a realtime translator.&lt;/p&gt;

&lt;p&gt;It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlencode&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api.funtranslations.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;form_encoded_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/translate/piglatin.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;form_encoded_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;translated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Less than a dozen lines of code for marital bliss. Seems worthwhile.&lt;/p&gt;

&lt;p&gt;You might save the above as &lt;code&gt;pypiglatin.py&lt;/code&gt; in a directory of your choosing.&lt;/p&gt;

&lt;p&gt;The script and associated tests can be found &lt;a href="https://github.com/bowmanjd/pypiglatin" rel="noopener noreferrer"&gt;in the companion Github repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tests
&lt;/h2&gt;

&lt;p&gt;If you are like me, you have already tried the above enough times to receive a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429" rel="noopener noreferrer"&gt;429 "Too Many Requests" error&lt;/a&gt;. The &lt;a href="https://funtranslations.com/register" rel="noopener noreferrer"&gt;funtranslations.com service&lt;/a&gt; allows you to sign up and become a paying customer in order to remove the rate limiting, but the anonymous limits certainly make a point. Your many tests are using up someone's server bandwidth and compute cycles. Maybe we should stop doing that. Even if the API you are accessing isn't rate limited.&lt;/p&gt;

&lt;p&gt;Instead, find ways of testing locally. It will be faster, and kinder. Tests will make sure that the pig Latin translation is working smoothly, in advance of any high-pressure family gatherings.&lt;/p&gt;

&lt;p&gt;I believe tools for HTTP client testing should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As already mentioned, easily replicate API endpoints locally, for performance and respect of API limits.&lt;/li&gt;
&lt;li&gt;Locally test the same protocol (HTTPS or HTTP) that is used in production. Usually API endpoints are HTTPS only, and so the URLs, even in testing, should start with "https://"&lt;/li&gt;
&lt;li&gt;HTTP client library flexibility. Yes, &lt;a href="https://requests.readthedocs.io/" rel="noopener noreferrer"&gt;requests&lt;/a&gt; pairs well with &lt;a href="https://github.com/getsentry/responses" rel="noopener noreferrer"&gt;responses&lt;/a&gt; or &lt;a href="https://requests-mock.readthedocs.io/" rel="noopener noreferrer"&gt;requests-mock&lt;/a&gt;, and &lt;a href="https://www.python-httpx.org/" rel="noopener noreferrer"&gt;HTTPX&lt;/a&gt; has &lt;a href="https://lundberg.github.io/respx/" rel="noopener noreferrer"&gt;RESPX&lt;/a&gt; or &lt;a href="https://colin-b.github.io/pytest_httpx/" rel="noopener noreferrer"&gt;pytest_httpx&lt;/a&gt;. Those testing helpers are an excellent match for the corresponding library, and should certainly be recommended. However, I don't always want to face the risk of rewriting all my tests if I replace the client library some day. And sometimes I am using an altogether different tool (even though both &lt;a href="https://requests.readthedocs.io/" rel="noopener noreferrer"&gt;requests&lt;/a&gt; and &lt;a href="https://www.python-httpx.org/" rel="noopener noreferrer"&gt;HTTPX&lt;/a&gt; are quite awesome) such as &lt;a href="https://dev.to/bowmanjd/http-calls-in-python-without-requests-or-other-external-dependencies-5aj1"&gt;urlopen&lt;/a&gt;, &lt;a href="https://urllib3.readthedocs.io/" rel="noopener noreferrer"&gt;urllib3&lt;/a&gt;, &lt;a href="https://github.com/httplib2/httplib2" rel="noopener noreferrer"&gt;httplib2&lt;/a&gt;, &lt;a href="https://www.tornadoweb.org/en/stable/httpclient.html" rel="noopener noreferrer"&gt;tornado&lt;/a&gt;, or &lt;a href="https://docs.aiohttp.org/en/stable/client.html" rel="noopener noreferrer"&gt;aiohttp&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's explore two methods for meeting the above goals. Both assume the use of &lt;a href="https://docs.pytest.org/" rel="noopener noreferrer"&gt;pytest&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An easy-to-configure HTTP server using &lt;a href="https://pytest-httpserver.readthedocs.io/" rel="noopener noreferrer"&gt;pytest-httpserver&lt;/a&gt;, coupled with &lt;a href="https://trustme.readthedocs.io/" rel="noopener noreferrer"&gt;trustme&lt;/a&gt; for quick SSL certificate setup.&lt;/li&gt;
&lt;li&gt;While not an actual HTTP server, &lt;a href="https://pytest-vcr.readthedocs.io/" rel="noopener noreferrer"&gt;pytest-vcr&lt;/a&gt; records and then mocks HTTP requests using the &lt;a href="https://vcrpy.readthedocs.io/" rel="noopener noreferrer"&gt;VCR.py&lt;/a&gt; library.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  pytest-httpserver
&lt;/h2&gt;

&lt;p&gt;It is hard to beat an actual HTTP server for testing reassurance, and &lt;a href="https://pytest-httpserver.readthedocs.io/" rel="noopener noreferrer"&gt;pytest-httpserver&lt;/a&gt; provides a quick way to configure responses based on expected requests. The web application library &lt;a href="https://dev.tothe%20foundation%20of%20the%20[Flask]%20framework"&gt;Werkzeug&lt;/a&gt; is used to build the server.&lt;/p&gt;

&lt;p&gt;Here is an example that tests if a request to the "/hello" endpoint indeed returns "Hello, World!" as expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;


&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;httpserver_listen_address&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8888&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpserver&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, World!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;httpserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;respond_with_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use the above test, save it in your current directory as &lt;code&gt;test_http.py&lt;/code&gt; or similar, then install &lt;code&gt;pytest&lt;/code&gt; and &lt;code&gt;pytest-httpserver&lt;/code&gt; in your preferred manner (&lt;code&gt;pip install pytest pytest-httpserver&lt;/code&gt; works well in a &lt;a href="https://dev.to/bowmanjd/python-tools-for-managing-virtual-environments-3bko"&gt;Python virtual environment&lt;/a&gt;). Then run &lt;code&gt;pytest&lt;/code&gt; from the command line in that directory.&lt;/p&gt;

&lt;p&gt;While not strictly necessary, I like defining the &lt;code&gt;httpserver_listen_address&lt;/code&gt; fixture from &lt;code&gt;pytest-httpserver&lt;/code&gt; to explicitly define the server and port. This makes it easy to define URLs later, as the port is known. Otherwise, &lt;code&gt;pytest-httpserver&lt;/code&gt; will assign a random port.&lt;/p&gt;

&lt;p&gt;The heart of the test server setup is the line&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;httpserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;respond_with_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We tell the server what sort of request to expect, and the data with which to respond to the expected request. This is quite configurable, on both ends. See &lt;a href="https://pytest-httpserver.readthedocs.io/en/latest/tutorial.html#specifying-the-expectations-and-constraints" rel="noopener noreferrer"&gt;the docs&lt;/a&gt; for more advanced examples. For instance, request and/or response headers can be defined, the method (GET, POST, PUT, DELETE) specified, and various formats of data can be expected or returned, such as JSON.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding SSL with trustme
&lt;/h3&gt;

&lt;p&gt;What the sample test above is missing is HTTPS. Thankfully, &lt;code&gt;pytest-httpserver&lt;/code&gt; allows a Python &lt;a href="https://docs.python.org/3/library/ssl.html#ssl.SSLContext" rel="noopener noreferrer"&gt;&lt;code&gt;ssl.SSLContext&lt;/code&gt;&lt;/a&gt; to be specified by defining the &lt;code&gt;httpserver_ssl_context&lt;/code&gt; fixture.&lt;/p&gt;

&lt;p&gt;However, building a functioning certificate authority and the rest of the cert chain is not exactly a trivial matter. Enter &lt;a href="https://trustme.readthedocs.io/" rel="noopener noreferrer"&gt;trustme&lt;/a&gt;, a Python-based tool for enabling SSL for testing purposes. You can install it in your virtual environment with &lt;code&gt;pip install trustme&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is a test mechanism for our previously-built &lt;code&gt;translate&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlencode&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trustme&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pypiglatin&lt;/span&gt;

&lt;span class="n"&gt;SERVER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8888&lt;/span&gt;
&lt;span class="n"&gt;ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/translate/piglatin.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;httpserver_listen_address&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SERVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;httpserver_ssl_context&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;ca&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trustme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CA&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;client_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SSLContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;server_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SSLContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;server_cert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ca&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;issue_cert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test-host.example.org&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ca&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure_trust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;server_cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure_cert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;default_context&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client_context&lt;/span&gt;

    &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_create_default_https_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;default_context&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;server_context&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpserver&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Thank you for your hospitality&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;translated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ank-Thay ou-yay or-fay our-yay ospitality-hay &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;translated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;translation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pig-latin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;httpserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;respond_with_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;pypiglatin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;translated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few notables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We take advantage of the &lt;code&gt;httpserver_ssl_context&lt;/code&gt; fixture to not only build a server SSL context and return it, but also a build a client context that trusts the server cert. We then make &lt;code&gt;ssl._create_default_https_context()&lt;/code&gt; return that client context. Thus, any HTTPS client that seeks to utilize the default context from the &lt;code&gt;ssl&lt;/code&gt; module will respect our new cert authority.&lt;/li&gt;
&lt;li&gt;If your client does not use the &lt;code&gt;ssl&lt;/code&gt; module to provide an SSL context, you will want to find another way to pass the client SSL context to your HTTP client. If need be, see the &lt;a href="https://trustme.readthedocs.io/" rel="noopener noreferrer"&gt;trustme&lt;/a&gt; documentation on how to write the certs to files so they can be utilized later.&lt;/li&gt;
&lt;li&gt;The expected request is a little more complex than our &lt;code&gt;test_hello&lt;/code&gt; example. It expects &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt; POST data (encoded with &lt;a href="https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlencode" rel="noopener noreferrer"&gt;&lt;code&gt;urllib.parse.urlencode()&lt;/code&gt;&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;The response is a Python dict populated with a known API response, then converted to JSON by the &lt;code&gt;respond_with_json()&lt;/code&gt; method.&lt;/li&gt;
&lt;li&gt;In my tests, the server SSL context seemed to work as the client SSL context as well. It just didn't seem like the right way to do it, so I separated them. Feel free to post advice in the comments.&lt;/li&gt;
&lt;li&gt;This is only one test. We can add tests to make sure incorrect requests elicit an error, and other messages are translated correctly. Neither the production API server nor the in-laws will know how fast and how often we fail. Beauty.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The end result is a live local HTTPS service intended to behave very similarly to the remote production service when called with specific data. The primary difference is that the URL is "&lt;a href="https://localhost:8888/translate/piglatin.json" rel="noopener noreferrer"&gt;https://localhost:8888/translate/piglatin.json&lt;/a&gt;" when testing, but "&lt;a href="https://api.funtranslations.com/translate/piglatin.json" rel="noopener noreferrer"&gt;https://api.funtranslations.com/translate/piglatin.json&lt;/a&gt;" in production.&lt;/p&gt;

&lt;p&gt;For a nice introduction to &lt;code&gt;pytest-httpserver&lt;/code&gt;, &lt;a href="https://pytest-httpserver.readthedocs.io/en/latest/tutorial.html" rel="noopener noreferrer"&gt;see the tutorial&lt;/a&gt;. For a deeper dive, &lt;a href="https://pytest-httpserver.readthedocs.io/en/latest/howto.html" rel="noopener noreferrer"&gt;see the how-to&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  VCR.py
&lt;/h2&gt;

&lt;p&gt;I love the convenience and realism that &lt;code&gt;pytest-httpserver&lt;/code&gt; provides. The only downside is the mild tedium in recording and setting up responses and expected requests manually.&lt;/p&gt;

&lt;p&gt;An alternate approach involves using &lt;a href="https://vcrpy.readthedocs.io/" rel="noopener noreferrer"&gt;VCR.py&lt;/a&gt;, a tool that records HTTP interactions in YAML files, then intercepts future HTTP requests and plays back the recorded responses. In this tutorial, we will use &lt;a href="https://pytest-vcr.readthedocs.io/" rel="noopener noreferrer"&gt;pytest-vcr&lt;/a&gt; to interface with &lt;a href="https://vcrpy.readthedocs.io/" rel="noopener noreferrer"&gt;VCR.py&lt;/a&gt;, although &lt;a href="https://github.com/kiwicom/pytest-recording" rel="noopener noreferrer"&gt;pytest-recording&lt;/a&gt; is another good option for doing the same.&lt;/p&gt;

&lt;p&gt;While this doesn't provide an actual HTTPS server like &lt;code&gt;pytest-httpserver&lt;/code&gt; does, the URLs used in the client code do not need to change. They can still start with "&lt;code&gt;https://&lt;/code&gt;"; in fact, because the HTTPS calls are intercepted, URLs can use the same domains as production, without fear that calls will be emitted to the production servers (after the initial recording is saved).&lt;/p&gt;

&lt;p&gt;Unfortunately, VCR.py only supports &lt;a href="https://vcrpy.readthedocs.io/en/latest/installation.html#compatibility" rel="noopener noreferrer"&gt;a certain number of HTTP client libraries&lt;/a&gt;. Thankfully, some very popular implementations are included: the Python built-in &lt;code&gt;urllib&lt;/code&gt; and &lt;code&gt;http.client&lt;/code&gt;, as well as &lt;a href="https://requests.readthedocs.io/" rel="noopener noreferrer"&gt;requests&lt;/a&gt;, &lt;a href="https://urllib3.readthedocs.io/" rel="noopener noreferrer"&gt;urllib3&lt;/a&gt;, and &lt;a href="https://docs.aiohttp.org/en/stable/client.html" rel="noopener noreferrer"&gt;aiohttp&lt;/a&gt;. And, while it may not be listed in the docs at the time of this writing, &lt;a href="https://github.com/kevin1024/vcrpy/blob/v4.1.1/docs/changelog.rst" rel="noopener noreferrer"&gt;the changelog notes&lt;/a&gt; that &lt;a href="https://www.python-httpx.org/" rel="noopener noreferrer"&gt;HTTPX&lt;/a&gt; is also supported. Sadly, &lt;a href="http://pycurl.io/" rel="noopener noreferrer"&gt;pycurl&lt;/a&gt; is not supported.&lt;/p&gt;

&lt;p&gt;To use VCR.py with pytest, start by installing &lt;code&gt;pytest-vcr&lt;/code&gt; in your virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pytest-vcr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Implementing a test for the &lt;code&gt;translate()&lt;/code&gt; function we built is quite simple using &lt;a href="https://pytest-vcr.readthedocs.io/" rel="noopener noreferrer"&gt;pytest-vcr&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pypiglatin&lt;/span&gt;


&lt;span class="nd"&gt;@pytest.mark.vcr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_translate&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Thank you for your hospitality&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;translated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ank-Thay ou-yay or-fay our-yay ospitality-hay &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;pypiglatin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;translated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might save this as &lt;code&gt;test_vcr.py&lt;/code&gt; in the current directory. Now we can run &lt;code&gt;pytest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Afterward, there should be a &lt;code&gt;cassettes&lt;/code&gt; directory with a &lt;code&gt;test_translate.yaml&lt;/code&gt; file in it. Feel free to take a look: you will get an idea for the many facets of an HTTP request and response. The file is also easily editable, which could be useful, for instance, to munge authentication/authorization information (just do that before saving the file to version control, of course).&lt;/p&gt;

&lt;p&gt;If you run &lt;code&gt;pytest&lt;/code&gt; again, the test should run much faster, using the existing cassette.&lt;/p&gt;

&lt;p&gt;This YAML cassette can be saved to version control, alongside the tests, so that later runs on any machine will not need to hit the live server at all.&lt;/p&gt;

&lt;p&gt;And there it is. &lt;a href="https://vcrpy.readthedocs.io/" rel="noopener noreferrer"&gt;VCR.py&lt;/a&gt; is actually this easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improved family dynamics
&lt;/h2&gt;

&lt;p&gt;Hopefully, you are now gaining fluency in pig Latin. Meanwhile, you can be confident in your fallback plan: a thoroughly-tested translator, available at a moment's notice. Feel the reliability.&lt;/p&gt;

&lt;p&gt;Please feel free to leave comments with your experiences, questions, or marital advice!&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives
&lt;/h2&gt;

&lt;p&gt;Thes two methods are solid and flexible.&lt;/p&gt;

&lt;p&gt;Of course, as noted earlier, there are other options out there, especially if you use &lt;a href="https://requests.readthedocs.io/" rel="noopener noreferrer"&gt;requests&lt;/a&gt; or &lt;a href="https://www.python-httpx.org/" rel="noopener noreferrer"&gt;HTTPX&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;For &lt;a href="https://requests.readthedocs.io/" rel="noopener noreferrer"&gt;requests&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/getsentry/responses" rel="noopener noreferrer"&gt;responses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://requests-mock.readthedocs.io/" rel="noopener noreferrer"&gt;requests-mock&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;a href="https://www.python-httpx.org/" rel="noopener noreferrer"&gt;HTTPX&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://lundberg.github.io/respx/" rel="noopener noreferrer"&gt;RESPX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://colin-b.github.io/pytest_httpx/" rel="noopener noreferrer"&gt;pytest_httpx&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Also of interest
&lt;/h2&gt;

&lt;p&gt;If this article was helpful, you may also be interested in some of my other articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/bowmanjd/getting-started-with-httpx-part-2-pytest-and-pytesthttpx-2jef"&gt;Using pytest_httpx with HTTPX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/bowmanjd/python-dev-environment-part-2-testing-with-pytest-5a1h"&gt;Using pytest in a Python dev environment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>pytest</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Hardening and Simplifying Python's urlopen</title>
      <dc:creator>Jonathan Bowman</dc:creator>
      <pubDate>Wed, 10 Mar 2021 23:05:00 +0000</pubDate>
      <link>https://dev.to/bowmanjd/hardening-and-simplifying-python-s-urlopen-4gee</link>
      <guid>https://dev.to/bowmanjd/hardening-and-simplifying-python-s-urlopen-4gee</guid>
      <description>&lt;p&gt;I recently wrote &lt;a href="https://dev.to/bowmanjd/http-calls-in-python-without-requests-or-other-external-dependencies-5aj1"&gt;an article detailing the use of Python's &lt;code&gt;urlopen()&lt;/code&gt; for performing HTTP calls&lt;/a&gt;. While researching and writing, I learned of the &lt;a href="https://docs.python.org/3/library/urllib.request.html#urllib.request.OpenerDirector" rel="noopener noreferrer"&gt;OpenerDirector&lt;/a&gt; class. This class offers the opportunity to streamline &lt;a href="https://docs.python.org/3/library/urllib.request.html#urllib.request.urlopen" rel="noopener noreferrer"&gt;&lt;code&gt;urlopen()&lt;/code&gt;&lt;/a&gt;, make it more secure, and provide custom error handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security problems with the default &lt;code&gt;urlopen()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;You might try the following on a Linux system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file:///etc/passwd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A little disturbing, yes? &lt;a href="https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html?highlight=urlopen#b310-urllib-urlopen" rel="noopener noreferrer"&gt;Bandit agrees&lt;/a&gt;. Perhaps you want to consider scanning with &lt;a href="https://pypi.org/project/bandit/" rel="noopener noreferrer"&gt;that security tool&lt;/a&gt; or its related &lt;a href="https://github.com/tylerwince/flake8-bandit" rel="noopener noreferrer"&gt;flake8 plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The code above certainly communicates the lesson "sanitize your user inputs". Of course, if you control that &lt;code&gt;url&lt;/code&gt; string, or can ensure that it starts with the correct &lt;code&gt;https://&lt;/code&gt; scheme, then things are looking better. If this url comes from user input, though, it would be good to check for protocol at least, and, better yet, ensure that the domain is as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  A suggested solution
&lt;/h2&gt;

&lt;p&gt;The following code results in a &lt;code&gt;urlopen()&lt;/code&gt; command that only opens &lt;code&gt;https://&lt;/code&gt; URLs by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SafeOpener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenerDirector&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Iterable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;handlers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handlers&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnknownHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPDefaultErrorHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPRedirectHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPSHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPErrorProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;handler_class&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;handler_class&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;


&lt;span class="n"&gt;opener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SafeOpener&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;install_opener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running the above, using &lt;code&gt;urllib.request.urlopen()&lt;/code&gt; should fail with a &lt;code&gt;URLError&lt;/code&gt; if attempting to open &lt;code&gt;http:&lt;/code&gt;, &lt;code&gt;ftp:&lt;/code&gt;, &lt;code&gt;file:&lt;/code&gt;, &lt;code&gt;data:&lt;/code&gt;, or any other URL that doesn't have &lt;code&gt;https:&lt;/code&gt; at the beginning. It will still follow redirects automatically, just as &lt;code&gt;urlopen()&lt;/code&gt; does, and raise an exception for any HTTP status code that isn't in the 200s or 300s.&lt;/p&gt;

&lt;p&gt;By the way, if you prefer not to override the opener in the &lt;code&gt;urlopen()&lt;/code&gt; function, you could remove the &lt;code&gt;install_opener(opener)&lt;/code&gt; line. Then call &lt;code&gt;opener.open()&lt;/code&gt; instead of using &lt;code&gt;urlopen()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The above code assumes that all HTTP calls will be encrypted with TLS (aka "SSL", using &lt;code&gt;https:&lt;/code&gt;). That also means that testing will need to use &lt;code&gt;https:&lt;/code&gt; URLs as well. Consider using the &lt;a href="https://vcrpy.readthedocs.io/" rel="noopener noreferrer"&gt;vcr&lt;/a&gt; library to mock-reproduce HTTP calls, or the &lt;a href="https://trustme.readthedocs.io/" rel="noopener noreferrer"&gt;trustme&lt;/a&gt; library to actually set up certs for testing. If you need to use unencrypted &lt;code&gt;http:&lt;/code&gt; URLs, though, you can simply add &lt;code&gt;urllib.request.HTTPHandler&lt;/code&gt; to &lt;code&gt;handlers&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The handlers, defined
&lt;/h2&gt;

&lt;p&gt;This is the chain of default handlers normally used by &lt;code&gt;urlopen()&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ProxyHandler&lt;/code&gt;: searches system settings for proxies. If you are 100% sure that your tool or library will never be used with a proxy, then this is not necessary.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UnknownHandler&lt;/code&gt;: raises a &lt;a href="https://docs.python.org/3.8/library/urllib.error.html#urllib.error.URLError" rel="noopener noreferrer"&gt;URLError&lt;/a&gt; if the protocol requested in the URL is not supported by a handler in this chain. Very helpful and recommended.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HTTPHandler&lt;/code&gt;: handles unencrypted &lt;code&gt;http://&lt;/code&gt; connections. Only add this if you are sure you need URL support other than &lt;code&gt;https:&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HTTPDefaultErrorHandler&lt;/code&gt;: a prefilter of sorts that turns all responses into exceptions, for handling downstream. This is necessary unless you plan on handling statuses, exceptions, and redirects yourself.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HTTPRedirectHandler&lt;/code&gt;: handles redirects (status codes 301, 302, 303, or 307) and is necessary if automatic following of redirects is desired.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FTPHandler&lt;/code&gt;: handles &lt;code&gt;ftp:&lt;/code&gt; URLs. Not necessary for HTTP calls.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FileHandler&lt;/code&gt;: handles &lt;code&gt;file:&lt;/code&gt; URLs, and poses security risks. Rarely should this be necessary, if ever.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HTTPErrorProcessor&lt;/code&gt;: The final response handler, raising any non-20x (OK) responses&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DataHandler&lt;/code&gt;: handles &lt;code&gt;data:&lt;/code&gt; URLs. Hard to imagine why this would be necessary in normal use, and could pose potential security risks with user input.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As may be apparent, several of the above are rarely necessary, if ever, for HTTP API work. Instead, I recommend this list as a happy medium between security and usability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ProxyHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UnknownHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTTPHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTTPDefaultErrorHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTTPRedirectHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTTPSHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTTPErrorProcessor&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of the above, &lt;code&gt;ProxyHandler&lt;/code&gt; could possibly be removed if you know you don't need it, and even &lt;code&gt;HTTPHandler&lt;/code&gt; could be removed if you know that only &lt;code&gt;https:&lt;/code&gt; URLs will be called. Actually, this is a pretty good combination: the point of &lt;code&gt;https:&lt;/code&gt; is to ensure that nothing is intercepting the connection, and that there are no proxies. So a most-secure list is the same as what is in the example code above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UnknownHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTTPDefaultErrorHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTTPRedirectHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTTPSHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTTPErrorProcessor&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Five handlers, not the original nine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flexibility
&lt;/h2&gt;

&lt;p&gt;The use cases for custom &lt;code&gt;OpenerDirector&lt;/code&gt; instances go beyond just security and simplicity. By subclassing &lt;a href="https://docs.python.org/3/library/urllib.request.html#urllib.request.BaseHandler" rel="noopener noreferrer"&gt;&lt;code&gt;BaseHandler&lt;/code&gt;&lt;/a&gt; then adding custom status handling methods with names like &lt;code&gt;http_error_401&lt;/code&gt;, you create your own handlers that then can be appended to the handler list. These can be used for authorization, retry cadence, and other goals.&lt;/p&gt;

&lt;p&gt;Curious how these suggestions work for you! Feel free to suggest improvements and share experiences in the comments.&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>HTTP Calls in Python Without Requests or Other External Dependencies</title>
      <dc:creator>Jonathan Bowman</dc:creator>
      <pubDate>Sun, 07 Mar 2021 22:44:17 +0000</pubDate>
      <link>https://dev.to/bowmanjd/http-calls-in-python-without-requests-or-other-external-dependencies-5aj1</link>
      <guid>https://dev.to/bowmanjd/http-calls-in-python-without-requests-or-other-external-dependencies-5aj1</guid>
      <description>&lt;p&gt;In addition to great &lt;a href="https://www.python.org/" rel="noopener noreferrer"&gt;Python&lt;/a&gt; HTTP client tools such as &lt;a href="https://requests.readthedocs.io/" rel="noopener noreferrer"&gt;Requests&lt;/a&gt; and &lt;a href="https://www.python-httpx.org/" rel="noopener noreferrer"&gt;HTTPX&lt;/a&gt;, the standard library itself supplies the necessary ingredients to make a working HTTP client for API calls. This tutorial shares how to construct and customize such a tool for your own scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consider installing a library
&lt;/h2&gt;

&lt;p&gt;Before proceeding, I should note that in many cases, the approach in this article is not best practice. Instead, I highly recommend using a third-party Python library for features, security, and reliability.&lt;/p&gt;

&lt;p&gt;Some suggested libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://urllib3.readthedocs.io/" rel="noopener noreferrer"&gt;urllib3&lt;/a&gt; is the dependency for many other tools, including &lt;a href="https://requests.readthedocs.io/" rel="noopener noreferrer"&gt;requests&lt;/a&gt;. By itself, &lt;a href="https://urllib3.readthedocs.io/" rel="noopener noreferrer"&gt;urllib3&lt;/a&gt; is quite usable. It may be all you need.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://requests.readthedocs.io/" rel="noopener noreferrer"&gt;requests&lt;/a&gt; is ubiquitous and well documented.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.python-httpx.org/" rel="noopener noreferrer"&gt;HTTPX&lt;/a&gt; has an interface almost identical to &lt;a href="https://requests.readthedocs.io/" rel="noopener noreferrer"&gt;requests&lt;/a&gt;, but with the added benefit of &lt;a href="https://docs.python.org/3/library/asyncio.html" rel="noopener noreferrer"&gt;asyncio&lt;/a&gt; support. You may be interested in &lt;a href="https://dev.to/bowmanjd/getting-started-with-httpx-building-a-python-rest-client-234n"&gt;a series of articles I wrote on using HTTPX&lt;/a&gt; both synchronously and asynchronously.&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://pycurl.io/" rel="noopener noreferrer"&gt;pycurl&lt;/a&gt; is less popular as a Python library, but interfaces with the well-known &lt;a href="https://curl.se/libcurl/" rel="noopener noreferrer"&gt;libcurl&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aiohttp.org/en/stable/client.html" rel="noopener noreferrer"&gt;aiohttp&lt;/a&gt; has an asyncio-based HTTP client that is well-documented and well-liked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If, however, you find yourself needing a solution that does not require external dependencies other than what is already available in the Python standard library, then you may wish to read on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.error&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;email.message&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;typing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamedTuple&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;error_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        Decode body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s JSON.

        Returns:
            Pythonic representation of the JSON object
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data_as_json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;error_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;casefold&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;URLError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Incorrect and possibly insecure protocol in url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;request_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doseq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;safe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data_as_json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;request_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json; charset=UTF-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;request_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;httprequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httprequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;httpresponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;httpresponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;httpresponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;httpresponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;httpresponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_content_charset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;error_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;error_count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are certainly welcome to copy and use the above function, or browse or clone &lt;a href="https://github.com/bowmanjd/pysimpleurl" rel="noopener noreferrer"&gt;the Github repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If, however, you are reading this article for a do-it-yourself approach, I encourage you to build your own function that suits your needs. It may grow simpler or more flexible than the above.&lt;/p&gt;

&lt;p&gt;Let's discuss the building blocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  An introduction to &lt;code&gt;urllib.request.urlopen()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The recommended high-level function for HTTP requests is &lt;a href="https://docs.python.org/3/library/urllib.request.html#urllib.request.urlopen" rel="noopener noreferrer"&gt;&lt;code&gt;urlopen()&lt;/code&gt;&lt;/a&gt;, available in the standard &lt;a href="https://docs.python.org/3/library/urllib.request.html" rel="noopener noreferrer"&gt;&lt;code&gt;urllib.request&lt;/code&gt;&lt;/a&gt; module.&lt;/p&gt;

&lt;p&gt;Unlike the lower-level &lt;a href="https://docs.python.org/3/library/http.client.html" rel="noopener noreferrer"&gt;&lt;code&gt;http.client&lt;/code&gt;&lt;/a&gt; module, &lt;a href="https://docs.python.org/3/library/urllib.request.html#urllib.request.urlopen" rel="noopener noreferrer"&gt;&lt;code&gt;urlopen()&lt;/code&gt;&lt;/a&gt; provides error handling, follows redirects, and provides convenience around headers and data.&lt;/p&gt;

&lt;p&gt;An example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://jsonplaceholder.typicode.com/posts/1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Incorrect and possibly insecure protocol in url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;httprequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httprequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I highly appreciate and recommend &lt;a href="https://jsonplaceholder.typicode.com/" rel="noopener noreferrer"&gt;JSONPlaceHolder&lt;/a&gt;'s free fake API, used above. Useful precisely for what we are doing here: testing HTTP clients intended for API work.&lt;/p&gt;

&lt;p&gt;Please note the security measures in the above code. Before passing a URL to &lt;code&gt;urlopen()&lt;/code&gt;, &lt;a href="https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html?highlight=urlopen#b310-urllib-urlopen" rel="noopener noreferrer"&gt;make sure that it is a web url and not a local file ("&lt;code&gt;file:///&lt;/code&gt;")&lt;/a&gt;. If you want a wake-up call, try &lt;code&gt;urlopen("file:///etc/passwd").read()&lt;/code&gt; on a Linux system (not in production code, though!) and see what happens. Of course, this protocol check is only necessary if the URL comes from user input. If you control the URL string and can assure that it does not start with "&lt;code&gt;file:&lt;/code&gt;" then that is a good thing. You may also be interested in an another approach to &lt;a href="https://dev.to/bowmanjd/hardening-and-simplifying-python-s-urlopen-4gee"&gt;hardening &lt;code&gt;urlopen()&lt;/code&gt; by redefining the list of protocol handlers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Protocol checking aside, the &lt;code&gt;urlopen()&lt;/code&gt; call is fairly simple, as you can see in the above example. I recommend using it alongside &lt;a href="https://docs.python.org/3/reference/compound_stmts.html#with" rel="noopener noreferrer"&gt;&lt;code&gt;with&lt;/code&gt; in a context manager&lt;/a&gt; for tidiness, so that closing the response is handled automatically.&lt;/p&gt;

&lt;p&gt;We passed a &lt;a href="https://docs.python.org/3/library/urllib.request.html#urllib.request.Request" rel="noopener noreferrer"&gt;Request&lt;/a&gt; object to the &lt;code&gt;urlopen()&lt;/code&gt; function. While we could simply pass a URL string, the &lt;a href="https://docs.python.org/3/library/urllib.request.html#urllib.request.Request" rel="noopener noreferrer"&gt;Request&lt;/a&gt; object offer much more flexibility: we can specify HTTP &lt;code&gt;method&lt;/code&gt; (GET, POST, PUT, HEAD, DELETE), &lt;a href="https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields" rel="noopener noreferrer"&gt;request &lt;code&gt;headers&lt;/code&gt;&lt;/a&gt;, and request &lt;code&gt;data&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.python.org/3/library/urllib.request.html#module-urllib.response" rel="noopener noreferrer"&gt;response&lt;/a&gt; returned by &lt;code&gt;urlopen&lt;/code&gt; has 4 useful attributes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It has a file-like interface that can be &lt;code&gt;read()&lt;/code&gt;, returning bytes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;url&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt; returns the &lt;a href="https://en.wikipedia.org/wiki/List_of_HTTP_status_codes" rel="noopener noreferrer"&gt;HTTP status code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;headers&lt;/code&gt; returns an &lt;a href="https://docs.python.org/3.8/library/email.message.html" rel="noopener noreferrer"&gt;EmailMessage&lt;/a&gt; object. This functions somewhat like a &lt;code&gt;dict&lt;/code&gt; but with case-insensitive keys. It also has some helpful methods such as &lt;a href="https://docs.python.org/3.8/library/email.message.html#email.message.EmailMessage.get_content_type" rel="noopener noreferrer"&gt;&lt;code&gt;get_content_type()&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://docs.python.org/3.8/library/email.message.html#email.message.EmailMessage.get_content_charset" rel="noopener noreferrer"&gt;&lt;code&gt;get_content_charset()&lt;/code&gt;&lt;/a&gt;. The &lt;a href="https://docs.python.org/3.8/library/email.message.html#email.message.EmailMessage.get_all" rel="noopener noreferrer"&gt;&lt;code&gt;get_all()&lt;/code&gt;&lt;/a&gt; method is another useful one, for when there may be multiple key/value pairs for the same header name. See &lt;a href="https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields" rel="noopener noreferrer"&gt;the helpful Wikipedia article&lt;/a&gt; for a list of possible reponse headers.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  HTTP errors
&lt;/h2&gt;

&lt;p&gt;Out of the box, &lt;code&gt;urlopen&lt;/code&gt; handles redirects (status codes 301, 302, 303, or 307). Other than these codes, though, if the status code is not &lt;a href="https://github.com/python/cpython/blob/v3.9.2/Lib/urllib/request.py#L1954" rel="noopener noreferrer"&gt;between 200 and 299&lt;/a&gt; (HTTP "OK" codes according to &lt;a href="https://tools.ietf.org/html/rfc2616#section-10.2" rel="noopener noreferrer"&gt;RFC 2616&lt;/a&gt;) then an &lt;a href="https://docs.python.org/3.8/library/urllib.error.html#urllib.error.HTTPError" rel="noopener noreferrer"&gt;HTTPError&lt;/a&gt; exception is raised.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.python.org/3.8/library/urllib.error.html#urllib.error.HTTPError" rel="noopener noreferrer"&gt;HTTPError&lt;/a&gt; can be captured and analyzed with the appropriate &lt;code&gt;try... except...&lt;/code&gt; block, such as this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.error&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HTTPError&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/404&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_content_type&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error (assigned to the "&lt;code&gt;e&lt;/code&gt;" variable in the above) has the following useful properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt; to get the error code (such as 404)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;headers&lt;/code&gt; as an &lt;a href="https://docs.python.org/3.8/library/email.message.html" rel="noopener noreferrer"&gt;EmailMessage&lt;/a&gt; object. Again, this can be treated like a case-insensitive &lt;code&gt;dict&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reason&lt;/code&gt; with the text of the error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the function at the top of this article, I catch and silence all errors, to make the response uniform, and pass error-handling responsibility downstream. However, this may not be desirable. Perhaps, instead of continuing no matter the error, you want to fail on anything other than a 401 or 429 error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;error_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;error_count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, logic could be added to deal with errors as appropriate, depending on the status code.&lt;/p&gt;

&lt;p&gt;Note the entirely optional auto-incrementing &lt;code&gt;error_count&lt;/code&gt; attribute in my code. Sometimes, I wish to call the http request function recursively. This allows the number of calls to be tracked, and dealt with downstream, hopefully preventing infinite recursion. For instance, I may want to catch 401 errors, parse the "Www-Authenticate" header for a token, then retry the request with the token. But if this fails repeatedly (say, 5 tries), it should stop. I could test for &lt;code&gt;error_count &amp;gt;= 5&lt;/code&gt; and raise and exception if so, meanwhile making sure to pass the current &lt;code&gt;error_count&lt;/code&gt; back to the request function as a parameter, so it continues to be incremented appropriately.&lt;/p&gt;

&lt;p&gt;An alternative way to customize error handling is to construct your own subclasses of &lt;a href="https://docs.python.org/3/library/urllib.request.html#urllib.request.BaseHandler" rel="noopener noreferrer"&gt;&lt;code&gt;BaseHandler&lt;/code&gt;&lt;/a&gt;, then &lt;a href="https://docs.python.org/3/library/urllib.request.html#urllib.request.build_opener" rel="noopener noreferrer"&gt;build an OpenerDirector&lt;/a&gt; chain of handlers as appropriate. For instance, you could subclass &lt;code&gt;BaseHandler&lt;/code&gt; and add a method &lt;code&gt;http_error_401&lt;/code&gt; to handle authorization as desired, then pass an instance of that custom class to &lt;code&gt;build_opener()&lt;/code&gt;. Obviously, this requires a deeper dive into the opener innards.&lt;/p&gt;

&lt;h2&gt;
  
  
  A versatile &lt;code&gt;Response&lt;/code&gt; object
&lt;/h2&gt;

&lt;p&gt;I find it helpful to create a Python class that can contain the bits of the HTTP response that I care about. This could be a &lt;code&gt;dict&lt;/code&gt;, but I like to add a method or two, such as a JSON decoder.&lt;/p&gt;

&lt;p&gt;If using Python 3.7 or later, consider using a &lt;a href="https://docs.python.org/3/library/dataclasses.html" rel="noopener noreferrer"&gt;&lt;code&gt;dataclass&lt;/code&gt;&lt;/a&gt;. An example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;error_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        Decode body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s JSON.

        Returns:
            Pythonic representation of the JSON object
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enabling &lt;code&gt;frozen&lt;/code&gt; is strictly optional, and reflects my preference for this object being immutable (attributes can't be changed after initialization).&lt;/p&gt;

&lt;p&gt;Another option, as demonstrated in the code at the beginning of the article, is a &lt;a href="https://docs.python.org/3/library/typing.html#typing.NamedTuple" rel="noopener noreferrer"&gt;typed NamedTuple&lt;/a&gt;. I chose this for its immutability, ease of setup, and backwards compatibility.&lt;/p&gt;

&lt;p&gt;Of course, a custom class will work, or &lt;a href="https://www.attrs.org/" rel="noopener noreferrer"&gt;attrs&lt;/a&gt;, or whatever container works for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requests with data
&lt;/h2&gt;

&lt;p&gt;Depending on the API with which you are interfacing, you may encounter various scenarios for accepting data. In each scenario, we can start with a Python &lt;code&gt;dict&lt;/code&gt; and convert it into the required format.&lt;/p&gt;

&lt;h3&gt;
  
  
  The query string
&lt;/h3&gt;

&lt;p&gt;Sometimes, data is passed in through the query string. To encode a Python &lt;code&gt;dict&lt;/code&gt; as a query string that can be appended to the URL (after the "&lt;code&gt;?&lt;/code&gt;"), use &lt;a href="https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlencode" rel="noopener noreferrer"&gt;&lt;code&gt;urllib.parse.urlencode&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlencode&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://jsonplaceholder.typicode.com/posts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;userId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doseq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;safe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While not relevant to the above request, I did pass two parameters to &lt;code&gt;urlencode&lt;/code&gt; that I have found helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;doseq&lt;/code&gt; will allow lists to be encoded as multiple parameters. For instance, if we passed in &lt;code&gt;{"usernames": ["John Doe", "Jane Doe"]}&lt;/code&gt;, then the end result would be "&lt;code&gt;usernames=John+Doe&amp;amp;usernames=Jane+Doe&lt;/code&gt;".&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;safe&lt;/code&gt; defines the characters that will not be url-encoded. In some APIs I encounter, such as the Docker API, it is better to leave slashes unencoded, so I added that to the &lt;code&gt;safe&lt;/code&gt; string. Adapt as you see fit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sending data in the request body
&lt;/h3&gt;

&lt;p&gt;Similarly, data can be encoded with &lt;a href="https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlencode" rel="noopener noreferrer"&gt;&lt;code&gt;urllib.parse.urlencode&lt;/code&gt;&lt;/a&gt; and then passed into the Request object via the &lt;code&gt;data&lt;/code&gt; parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlencode&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.funtranslations.com/translate/yoda.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP POST calls are remarkably easy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;postdata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;httprequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;postdata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httprequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sending JSON in the request body
&lt;/h2&gt;

&lt;p&gt;Many APIs accept and even require the request parameters to be sent as JSON. In these cases, it is important to first encode the Python &lt;code&gt;dict&lt;/code&gt; (or other object) as JSON, then set the "&lt;code&gt;Content-Type&lt;/code&gt;" request header appropriately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlencode&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://jsonplaceholder.typicode.com/posts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;userid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POSTing JSON for Fun and Profit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JSON in the request body! Don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t forget the content type.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;postdata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json; charset=UTF-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;httprequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;postdata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httprequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above, we used &lt;a href="https://docs.python.org/3/library/json.html" rel="noopener noreferrer"&gt;Python's built-in JSON module&lt;/a&gt; to dump the data &lt;code&gt;dict&lt;/code&gt; into a string, then encode it to bytes so it could then handled as POST data.&lt;/p&gt;

&lt;p&gt;We set the content type header to &lt;code&gt;application/json&lt;/code&gt;. In addition, we specified the character encoding as UTF-8. Given that UTF-8 is &lt;a href="https://tools.ietf.org/html/rfc8259#section-8.1" rel="noopener noreferrer"&gt;the required JSON character encoding&lt;/a&gt;, this is redundant and probably &lt;a href="https://tools.ietf.org/html/rfc8259#section-11" rel="noopener noreferrer"&gt;unnecessary&lt;/a&gt;, but it never hurts to be explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing JSON in the response body
&lt;/h2&gt;

&lt;p&gt;Because most APIs I use return JSON, and some return other formats such as XML unless JSON is specified, I typically set the &lt;code&gt;Accepts&lt;/code&gt; header in the request to &lt;code&gt;application/json&lt;/code&gt;. If you are pulling other types of data, such as &lt;code&gt;text/csv&lt;/code&gt;, you would want to tweak that header. Set it to &lt;code&gt;*/*&lt;/code&gt; if you don't care.&lt;/p&gt;

&lt;p&gt;In the Response object we created earlier, there is an example of a JSON decoder, the result of which will likely be a Python &lt;code&gt;dict&lt;/code&gt; or &lt;code&gt;list&lt;/code&gt;, but could conceivably be a string or boolean, depending on what the server returns. Here is another example of similar functionality, but loading the JSON directly from the file-like &lt;a href="https://docs.python.org/3/library/urllib.request.html#module-urllib.response" rel="noopener noreferrer"&gt;response&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlencode&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://jsonplaceholder.typicode.com/posts?_limit=3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;jsonbody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;jsonbody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonbody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example I decided to fail silently, offering an empty string when JSON decoding fails. If this is not desired, just use &lt;code&gt;json.load()&lt;/code&gt; (or &lt;code&gt;json.loads()&lt;/code&gt; from a string) and let any exceptions float up as they occur.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other tricks or suggestions?
&lt;/h2&gt;

&lt;p&gt;I am very curious if you use &lt;code&gt;urlopen&lt;/code&gt; and how. Are there optimizations to the above that I am missing? Does this raise any questions or confusion? Feel free to post in the comments.&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>A "POSIX Playground" Container for Shell Script Testing</title>
      <dc:creator>Jonathan Bowman</dc:creator>
      <pubDate>Tue, 23 Feb 2021 11:36:27 +0000</pubDate>
      <link>https://dev.to/bowmanjd/a-posix-playground-container-for-shell-script-testing-2ba3</link>
      <guid>https://dev.to/bowmanjd/a-posix-playground-container-for-shell-script-testing-2ba3</guid>
      <description>&lt;p&gt;After exploring &lt;a href="https://www.bowmanjd.com/bash-not-bash-posix/"&gt;portable scripting in the previous article&lt;/a&gt;, I built a &lt;a href="https://hub.docker.com/repository/docker/bowmanjd/posix-playground/"&gt;container available on Docker Hub&lt;/a&gt;, useful for testing and experimentation.&lt;/p&gt;

&lt;p&gt;The container is an &lt;a href="https://alpinelinux.org/"&gt;Alpine Linux&lt;/a&gt; container with the following tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://manpages.debian.org/unstable/posh/posh.1.en.html"&gt;posh&lt;/a&gt; shell (this is the default)&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://gondor.apana.org.au/~herbert/dash/"&gt;dash&lt;/a&gt; shell&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/hanslub42/rlwrap"&gt;rlwrap&lt;/a&gt; to offer readline support for either of the above shells (&lt;code&gt;rlwrap posh&lt;/code&gt; is the default command if none specified)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://manpages.debian.org/unstable/devscripts/checkbashisms.1.en.html"&gt;checkbashisms&lt;/a&gt; for rooting out Bash-specific idiosyncrasies in scripts&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.shellcheck.net/"&gt;shellcheck&lt;/a&gt; for shell script linting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the &lt;a href="https://www.bowmanjd.com/bash-not-bash-posix/"&gt;"Writing Bash Scripts that are not only Bash"&lt;/a&gt; for explanation and usage of the above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the container
&lt;/h2&gt;

&lt;p&gt;(&lt;a href="https://podman.io/"&gt;Podman&lt;/a&gt; also works fine in any of the examples below.)&lt;/p&gt;

&lt;p&gt;First, pull the image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull docker.io/bowmanjd/posix-playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the default user is &lt;code&gt;shelly&lt;/code&gt; and the home directory is &lt;code&gt;/home/shelly&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To launch &lt;a href="https://manpages.debian.org/unstable/posh/posh.1.en.html"&gt;posh: Policy-compliant Ordinary SHell&lt;/a&gt; with readline support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; bowmanjd/posix-playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To instead launch &lt;a href="http://gondor.apana.org.au/~herbert/dash/"&gt;dash&lt;/a&gt; shell with readline support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; bowmanjd/posix-playground rlwrap dash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Without &lt;code&gt;rlwrap&lt;/code&gt;, line editing in either of these shells is impossible, making experimentation rather painful. Using &lt;code&gt;rlwrap&lt;/code&gt; is recommended.)&lt;/p&gt;

&lt;p&gt;Note that the container runs as an unprivileged user (as common sense dictates!) named &lt;code&gt;shelly&lt;/code&gt; and the home directory is &lt;code&gt;/home/shelly&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To make every file in the host's current working directory available from within the home directory inside the container, you might bind mount the current directory like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:/home/shelly"&lt;/span&gt; bowmanjd/posix-playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If using rootless &lt;a href="https://dev.toand%20likely%20SELinux"&gt;podman&lt;/a&gt;, you will find it convenient to add the &lt;code&gt;:z&lt;/code&gt; SELinux label to the bind mount to indicate a shared volume, allowing read-write acces, then use the &lt;code&gt;keep-id&lt;/code&gt; user namespace in order to map the user id of &lt;code&gt;shelly&lt;/code&gt; to your current user, as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;podman run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--userns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;keep-id &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:/home/shelly:z"&lt;/span&gt; bowmanjd/posix-playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To check a &lt;code&gt;helloworld.sh&lt;/code&gt; shell script in the current directory for "bashisms" (make sure the first line, the "&lt;a href="https://en.wikipedia.org/wiki/Shebang_(Unix)"&gt;shebang&lt;/a&gt;", reads &lt;code&gt;#!/bin/sh&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;helloworld.sh | docker run &lt;span class="nt"&gt;-i&lt;/span&gt; bowmanjd/posix-playground checkbashisms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To check a &lt;code&gt;helloworld.sh&lt;/code&gt; shell script in the current directory for a variety of gotchas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;helloworld.sh | docker run &lt;span class="nt"&gt;-i&lt;/span&gt; bowmanjd/posix-playground shellcheck -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Be sure to include the &lt;code&gt;-&lt;/code&gt; on the end: &lt;code&gt;shellcheck -&lt;/code&gt; to let shellcheck know to expect stdin, as you are "piping" the script file contents into shellcheck as standard input.&lt;/p&gt;

&lt;p&gt;To simply run a script using &lt;a href="https://manpages.debian.org/unstable/posh/posh.1.en.html"&gt;posh&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:/home/shelly"&lt;/span&gt; bowmanjd/posix-playground posh myscript.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Posh: an obscure shell with great usefulness
&lt;/h2&gt;

&lt;p&gt;Of the tools included in the image, &lt;a href="https://manpages.debian.org/unstable/posh/posh.1.en.html"&gt;posh&lt;/a&gt; is the winner for me. It is not available on every distro, so having a container available is quite convenient.&lt;/p&gt;

&lt;p&gt;Posh is also quite strict, and this is the point. It does not have any syntactic sugar to make it more Bash-like. I feel quite reassured when a script runs correctly in posh.&lt;/p&gt;

&lt;p&gt;If you have other tools to recommend, or want to reflect on your experiences with this container image, feel free to contact me!&lt;/p&gt;

</description>
      <category>bash</category>
      <category>linux</category>
      <category>docker</category>
    </item>
    <item>
      <title>Writing Bash Scripts that are not only Bash: Checking for Bashisms and testing with Dash</title>
      <dc:creator>Jonathan Bowman</dc:creator>
      <pubDate>Sun, 21 Feb 2021 20:52:25 +0000</pubDate>
      <link>https://dev.to/bowmanjd/writing-bash-scripts-that-are-not-only-bash-checking-for-bashisms-and-testing-with-dash-1bli</link>
      <guid>https://dev.to/bowmanjd/writing-bash-scripts-that-are-not-only-bash-checking-for-bashisms-and-testing-with-dash-1bli</guid>
      <description>&lt;p&gt;Shells like &lt;a href="https://www.gnu.org/software/bash/"&gt;Bash&lt;/a&gt; or &lt;a href="http://zsh.sourceforge.net/"&gt;Zsh&lt;/a&gt; are advanced and user-friendly, and include features beyond what a simpler POSIX-compliant shell might offer. You will do well to utilize the full features of your shell when writing scripts.&lt;/p&gt;

&lt;p&gt;There are situations, however, when &lt;em&gt;portability&lt;/em&gt; should be a valued feature, allowing the script to run on a variety of shells.&lt;/p&gt;

&lt;p&gt;Bash scripts are most portable when "bashisms" are avoided. Let's explore writing POSIX-compliant shell scripts that work on &lt;a href="http://gondor.apana.org.au/~herbert/dash/"&gt;Ash/Dash&lt;/a&gt; and other shells.&lt;/p&gt;

&lt;h2&gt;
  
  
  A summary checklist
&lt;/h2&gt;

&lt;p&gt;Here is a checklist I use to keep tabs on my own script writing. Does my script:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Have &lt;code&gt;#!/bin/sh&lt;/code&gt; as the first ("&lt;a href="https://en.wikipedia.org/wiki/Shebang_(Unix)"&gt;shebang&lt;/a&gt;") line of the script, not &lt;code&gt;#!/usr/bin/bash&lt;/code&gt; or other shell&lt;/li&gt;
&lt;li&gt;Avoid double-bracket tests &lt;code&gt;[[ ]]&lt;/code&gt; and instead use single-brackets &lt;code&gt;[ ]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;printf&lt;/code&gt; instead of &lt;code&gt;echo -e&lt;/code&gt; when newlines &lt;code&gt;'\n'&lt;/code&gt; need to be printed&lt;/li&gt;
&lt;li&gt;Use no other &lt;code&gt;read&lt;/code&gt; flag other than &lt;code&gt;-r&lt;/code&gt;, as in &lt;code&gt;read -r&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Avoid Bash's convenience redirects: use &lt;code&gt;&amp;gt;myfile 2&amp;gt;&amp;amp;1&lt;/code&gt; to redirect stdout and stderr to a file rather than &lt;code&gt;&amp;amp;&amp;gt;myfile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Test accurately with &lt;a href="http://gondor.apana.org.au/~herbert/dash/"&gt;dash&lt;/a&gt; or &lt;a href="https://manpages.debian.org/unstable/posh/posh.1.en.html"&gt;posh: Policy-compliant Ordinary SHell&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Only use standard flags and options with common utilities such as &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html"&gt;sed&lt;/a&gt;, &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html"&gt;grep&lt;/a&gt;, &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/cut.html"&gt;cut&lt;/a&gt;, &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html"&gt;test&lt;/a&gt;, &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/contents.html"&gt;and others&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Avoid issues discovered by &lt;a href="https://www.shellcheck.net/"&gt;shellcheck&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  An example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Who would you like to greet? "&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="nv"&gt;$REPLY&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;recipient&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REPLY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nv"&gt;recipient&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"World"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"Hello&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="nv"&gt;$recipient&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might save the following in the current working directory of your choice, as &lt;code&gt;example-noncompliant.sh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In human language, the above script prompts for a greeting recipient, then sets the recipient to "World" if none was given, then greets the recipient on multiple lines.&lt;/p&gt;

&lt;p&gt;The above works on Bash, but has issues on other shells. You may wish to run it once in Bash, just to feel good. Even in &lt;a href="http://zsh.sourceforge.net/"&gt;Zsh&lt;/a&gt;, though, it may raise some complaints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dash, a POSIX compliant shell
&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://gondor.apana.org.au/~herbert/dash/"&gt;Dash&lt;/a&gt; is a derivative of the &lt;a href="https://dev.toAlmquist,%20named%20for%20the%20original%20creator"&gt;Ash&lt;/a&gt; shell. It is meant to be &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/"&gt;POSIX&lt;/a&gt;-compliant.&lt;/p&gt;

&lt;p&gt;Debian and Ubuntu come with dash installed. In fact, scripts invoked with &lt;code&gt;/bin/sh&lt;/code&gt; will run with dash by default. On &lt;a href="https://alpinelinux.org/"&gt;Alpine&lt;/a&gt;, &lt;a href="http://tinycorelinux.net/"&gt;Tiny Core Linux&lt;/a&gt;, &lt;a href="https://openwrt.org/"&gt;OpenWRT&lt;/a&gt;, and other distros that use &lt;a href="https://busybox.net/"&gt;BusyBox&lt;/a&gt; by default, the standard shell is also dash (although labeled as &lt;code&gt;ash&lt;/code&gt;). On Fedora, dash can be installed with &lt;code&gt;sudo dnf install dash&lt;/code&gt;. Other distros may also include dash in their repositories.&lt;/p&gt;

&lt;p&gt;Using &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt; or &lt;a href="https://podman.io/"&gt;Podman&lt;/a&gt;, running dash is easy, as in this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; debian dash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may also try my &lt;a href="https://www.bowmanjd.com/posix-playground-container/"&gt;POSIX playground container&lt;/a&gt;, with a variety of tools, including &lt;a href="http://gondor.apana.org.au/~herbert/dash/"&gt;dash&lt;/a&gt;. By default, it uses &lt;a href="https://manpages.debian.org/unstable/posh/posh.1.en.html"&gt;posh: Policy-compliant Ordinary SHell&lt;/a&gt;, which is slightly stricter than dash. It can be launched with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; docker.io/bowmanjd/posix-playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See &lt;a href="https://www.bowmanjd.com/posix-playground-container/"&gt;the article&lt;/a&gt; for a deeper explanation.&lt;/p&gt;

&lt;p&gt;In all of the above, &lt;code&gt;podman&lt;/code&gt; can replace &lt;code&gt;docker&lt;/code&gt; without a problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the example
&lt;/h2&gt;

&lt;p&gt;Can you try running the &lt;code&gt;example-noncompliant.sh&lt;/code&gt; script above, but with dash, not Bash?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dash example-noncompliant.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, using Docker or Podman:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:/work"&lt;/span&gt; debian dash /work/example-noncompliant.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is likely something resembling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dash: 3: read: arg count
dash: 5: [[: not found
-e Hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few learning points can be derived from that output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid double-bracket tests &lt;code&gt;[[ ]]&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;[[&lt;/code&gt; construct is a safe one if using Bash or another shell that supports bashisms. It has some convenient features, like regex matching using &lt;code&gt;=~&lt;/code&gt;, and has less risks with string matching.&lt;/p&gt;

&lt;p&gt;That said, you will generally not go wrong with the single bracket approach: &lt;code&gt;[ ]&lt;/code&gt; (an alias for &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html"&gt;&lt;code&gt;test&lt;/code&gt;&lt;/a&gt;). Always be sure to quote variables, but that is good advice anyway. If you need regular express matching, use &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html"&gt;&lt;code&gt;grep&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Bottom line: not all shells support &lt;code&gt;[[&lt;/code&gt;; use &lt;code&gt;[&lt;/code&gt; instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use vanilla &lt;code&gt;read&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;When using the &lt;code&gt;read&lt;/code&gt; command to get input, here are a few suggestions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Always specify the variable, rather than relying on Bash's default &lt;code&gt;$REPLY&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;read -r&lt;/code&gt; and no other flags. Using &lt;code&gt;-r&lt;/code&gt; prohibits the user from using backslash to escape characters, which can cause issues later. And no other flag is &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html"&gt;supported by POSIX &lt;code&gt;read&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Instead of specifying a prompt with &lt;code&gt;-p&lt;/code&gt;, just use a &lt;code&gt;printf&lt;/code&gt; call prior to the &lt;code&gt;read&lt;/code&gt; command. Again, &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html"&gt;POSIX &lt;code&gt;read&lt;/code&gt;&lt;/a&gt; does not support such a flag, plus the &lt;code&gt;-p&lt;/code&gt; option means something different to &lt;a href="http://zsh.sourceforge.net/Doc/Release/Shell-Builtin-Commands.html"&gt;Zsh's &lt;code&gt;read&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Given these rules, our script should not use &lt;code&gt;read -p "Who would you like to greet? "&lt;/code&gt; but rather:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"Who would you like to greet? "&lt;/span&gt;
&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; recipient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Use &lt;code&gt;printf&lt;/code&gt; when newlines are at issue
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;echo&lt;/code&gt; command works great when we know we want to output a simple string, followed by a newline.&lt;/p&gt;

&lt;p&gt;However, if we have newlines in a string we want to print, or if printing without a trailing newline is desired, then &lt;code&gt;printf&lt;/code&gt;, not &lt;code&gt;echo -e&lt;/code&gt; will be our friend.&lt;/p&gt;

&lt;p&gt;So, instead of &lt;code&gt;echo -e "Hello\n$recipient\n"&lt;/code&gt; in our code above, this would be better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"Hello&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;%s&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$recipient&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the variable substitution going on with &lt;code&gt;%s&lt;/code&gt; in the first string (the format string). Do not put shell variables like &lt;code&gt;$recipient&lt;/code&gt; in the format string. This is the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refactoring the example
&lt;/h2&gt;

&lt;p&gt;Given the above concerns, let's completely rewrite our greeting script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"Who would you like to greet? "&lt;/span&gt;
&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; recipient

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$recipient&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;recipient&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"World"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"Hello&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;%s&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$recipient&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might save the above with the filename &lt;code&gt;example-posix.sh&lt;/code&gt; or similar.&lt;/p&gt;

&lt;p&gt;When you run it, does it behave the same as the noncompliant script? Hopefully not; try it out.&lt;/p&gt;

&lt;p&gt;Satisfying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the bashisms with &lt;code&gt;checkbashisms&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;There is a tool embedded in the &lt;a href="https://github.com/Debian/devscripts"&gt;Debian devscripts&lt;/a&gt; project, called &lt;a href="https://manpages.debian.org/unstable/devscripts/checkbashisms.1.en.html"&gt;checkbashisms&lt;/a&gt;. It is a simple but powerful &lt;a href="https://github.com/Debian/devscripts/blob/master/scripts/checkbashisms.pl"&gt;Perl script&lt;/a&gt; that ferrets out any bashisms in a shell script that begins with the &lt;code&gt;#!/bin/sh&lt;/code&gt; &lt;a href="https://en.wikipedia.org/wiki/Shebang_(Unix)"&gt;shebang&lt;/a&gt; line.&lt;/p&gt;

&lt;p&gt;On Debian and Ubuntu, it can be installed with &lt;code&gt;sudo apt install devscripts&lt;/code&gt; and on Fedora with &lt;code&gt;sudo dnf install devscripts-checkbashisms&lt;/code&gt; while Alpine is &lt;code&gt;sudo apk add checkbashisms&lt;/code&gt;. Other distros may have something similar. You might also try installing Perl, then downloading and running &lt;a href="https://github.com/Debian/devscripts/blob/master/scripts/checkbashisms.pl"&gt;the checkbashisms Perl script&lt;/a&gt; itself.&lt;/p&gt;

&lt;p&gt;What happens when you run it on &lt;code&gt;example-noncompliant.sh&lt;/code&gt; or &lt;code&gt;example-posix.sh&lt;/code&gt;? So telling...&lt;/p&gt;

&lt;h2&gt;
  
  
  Pursuing best practices with &lt;code&gt;shellcheck&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;My new favorite shell scripting helper is &lt;a href="https://www.shellcheck.net/"&gt;Shellcheck&lt;/a&gt;. You can paste your shell script online and check it there, or install shellcheck in the usual way (Debian, Ubuntu, Fedora, Alpine, Archlinux, and others have it readily available in the standard package repositories.)&lt;/p&gt;

&lt;p&gt;It does raise the POSIX-compliance flag on any lines that need it, but many other issues are checked as well. Your code might run just fine, but have gotchas that need some attention. &lt;a href="https://www.shellcheck.net/"&gt;Shellcheck&lt;/a&gt; will help you there. I integrate it into my editor, so that I can lint while I type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure against the POSIX specification
&lt;/h2&gt;

&lt;p&gt;Thankfully, the &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/"&gt;POSIX.1-2017 standard&lt;/a&gt; is openly documented. Consider this: when discovering and testing options for a given tool like &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html"&gt;&lt;code&gt;read&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html"&gt;&lt;code&gt;grep&lt;/code&gt;&lt;/a&gt;, or &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html"&gt;&lt;code&gt;sed&lt;/code&gt;&lt;/a&gt;, instead of going to the GNU pages, the distro man pages, or the Bash or Zsh docs, why not go to the POSIX spec itself? &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/contents.html"&gt;The list of utilities and their options&lt;/a&gt; is plainly explained.&lt;/p&gt;

&lt;p&gt;In instances where you really need an enhancement provided by the extended tools, you can make that choice. With the POSIX spec in hand, it becomes an informed decision.&lt;/p&gt;

&lt;p&gt;Often, I find that I don't need &lt;code&gt;sed -E&lt;/code&gt; or &lt;code&gt;grep -E&lt;/code&gt; as badly as I thought. A few extra escape characters, and I am there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consider other languages and configuration tools
&lt;/h2&gt;

&lt;p&gt;Sometimes, when the extended syntax provided by GNU utilities is warranted, it may be a sign that the right tool for the job isn't the shell and its compatriots at all.&lt;/p&gt;

&lt;p&gt;If your system has &lt;a href="https://www.python.org/"&gt;Python&lt;/a&gt;, &lt;a href="https://www.ruby-lang.org/"&gt;Ruby&lt;/a&gt;, &lt;a href="https://nodejs.org/"&gt;NodeJS&lt;/a&gt;, or other favorite language, might that be a more robust, flexible, and consistent option? Even in circumstances (embedded systems) in which those runtimes would be too bulky, perhaps remote scripting from another machine is in order. For instance, one could use Python to SSH to the remote machine, gain the information necessary, perform some logic, then send the appropriate commands back, without Python being necessary on the target machine.&lt;/p&gt;

&lt;p&gt;This is the reason such tools as &lt;a href="https://www.ansible.com/"&gt;Ansible&lt;/a&gt;, &lt;a href="https://saltproject.io/"&gt;Saltstack&lt;/a&gt;, &lt;a href="https://www.chef.io/"&gt;Chef&lt;/a&gt;, and &lt;a href="https://puppet.com/"&gt;Puppet&lt;/a&gt; exist. These, too, can be quite bloated if the needs are simple. But they are unbeatable for flexibility and repeatability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other resources
&lt;/h2&gt;

&lt;p&gt;In my research for this article, I encountered some resources you may find at least as interesting as this one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The informative and well-written &lt;a href="https://www.usenix.org/system/files/login/articles/login_spring16_09_tomei.pdf"&gt;A Brief POSIX Advocacy: Shell Script Portability&lt;/a&gt; by Arnaud Tomeï&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.70/autoconf.html#Portable-Shell"&gt;Autoconf portable shell guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.in-ulm.de/~mascheck/various/"&gt;Sven Mascheck's remarks on various Unix tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mywiki.wooledge.org/Bashism"&gt;How to make bash scripts work in dash&lt;/a&gt; by Greg Wooledge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Please feel free to share your tips, questions, corrections in the comments!&lt;/p&gt;

</description>
      <category>bash</category>
      <category>tutorial</category>
      <category>linux</category>
      <category>devops</category>
    </item>
    <item>
      <title>Install Docker on Windows (WSL) without Docker Desktop</title>
      <dc:creator>Jonathan Bowman</dc:creator>
      <pubDate>Sun, 14 Feb 2021 21:18:59 +0000</pubDate>
      <link>https://dev.to/bowmanjd/install-docker-on-windows-wsl-without-docker-desktop-34m9</link>
      <guid>https://dev.to/bowmanjd/install-docker-on-windows-wsl-without-docker-desktop-34m9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Updated April 10, 2022, with current Alpine instructions, Debian/Ubuntu package signing tweaks (no more apt-key), and better guidance for handling iptables in Debian. A little more suggestion about TCP access, as well. And further emphasis on the optional nature of the &lt;code&gt;/mnt/wsl/shared-docker&lt;/code&gt; socket directory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Windows Subsystem for Linux 2 sports an actual Linux kernel, supporting real Linux containers and Docker. Docker works on WSL 2, and &lt;em&gt;without&lt;/em&gt; requiring the robust but heavy Docker Desktop if that is undesirable. However, due to both WSL and Docker complexities, a little tender loving care is required to get Docker up and running. This article attempts to explore such a process and options along the way.&lt;/p&gt;

&lt;p&gt;Contrary to what the length of this article might suggest, getting Docker working on WSL is fairly simple. In a nutshell:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of using an init system such as systemd to launch the Docker daemon, launch it by calling &lt;code&gt;dockerd&lt;/code&gt; manually.&lt;/li&gt;
&lt;li&gt;If sharing the Docker daemon between WSL instances is desired, configure it to use a socket stored in the shared &lt;code&gt;/mnt/wsl&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;If sharing and privileged access without sudo are desired, configure the &lt;code&gt;docker&lt;/code&gt; group to have the same group ID across all WSL instances&lt;/li&gt;
&lt;li&gt;For simplicity, rather than launch a Windows-based Docker client, launch &lt;code&gt;docker&lt;/code&gt; inside WSL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plenty more nuance and decisions below, of course. See details regarding the &lt;a href="https://github.com/bowmanjd/docker-wsl" rel="noopener noreferrer"&gt;companion Github repo&lt;/a&gt; by scrolling to the bottom.&lt;/p&gt;

&lt;h2&gt;
  
  
  Are you sure you don't want Docker Desktop?
&lt;/h2&gt;

&lt;p&gt;Before proceeding, let's note that Docker Desktop is amazing. If you want Docker to work on Windows and WSL 2, &lt;a href="https://docs.docker.com/docker-for-windows/install/" rel="noopener noreferrer"&gt;installing Docker Desktop&lt;/a&gt; is most likely the way to go. With &lt;a href="https://docs.docker.com/docker-for-windows/wsl/" rel="noopener noreferrer"&gt;Docker Desktop's WSL 2 backend&lt;/a&gt;, Docker integrates with Windows in a fairly elegant way, and the docker client can be launched from either Powershell or Linux. &lt;a href="https://www.docker.com/blog/new-docker-desktop-wsl2-backend/" rel="noopener noreferrer"&gt;Reading about what goes on under the hood&lt;/a&gt; is an entertaining and informative endeavor, as well. Very clever.&lt;/p&gt;

&lt;p&gt;If you came here looking how to get Docker running easily, or if you want Windows containers (still a rarity) out of the box, then Docker Desktop is your friend, and you can &lt;a href="https://docs.docker.com/docker-for-windows/install/" rel="noopener noreferrer"&gt;go install it&lt;/a&gt; now.&lt;/p&gt;

&lt;p&gt;But if you, like me, feel that all the added complexity of Docker Desktop is unnecessary, you don't need Windows containers, or you are simply tired of that whale in the system tray taking... so... long... then perhaps you want to run the docker daemon (&lt;code&gt;dockerd&lt;/code&gt;) in the WSL distro of your choice and be happy. You are at the right place.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that Docker Desktop is only free individuals or for small companies. If you are using it for work, and your company exceeds a certain size or revenue, then consider paying for a subscription. Of course, if you use Docker without Docker Desktop, as detailed in this article, then this does not apply. &lt;a href="https://www.docker.com/blog/the-grace-period-for-the-docker-subscription-service-agreement-ends-soon-heres-what-you-need-to-know/" rel="noopener noreferrer"&gt;See more details about the Docker subscription model here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Have you tried Podman?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://podman.io/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs8ol1ckerpnl582s6egd.png" alt="Podman" width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before we mosey along, though: are you aware of &lt;a href="https://podman.io/" rel="noopener noreferrer"&gt;Podman&lt;/a&gt;? Podman is daemonless (no background service needed), modern (cgroups v2 out of the box), supports rootless, and serves as a drop-in replacement for Docker. Without needing to worry about sockets and ports, a lot of headaches go away.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/bowmanjd/using-podman-on-windows-subsystem-for-linux-wsl-58ji"&gt;I have written about getting Podman to work on WSL 2&lt;/a&gt;. Feel free to try it out. You may never look back. And, yes, VSCode can work with podman.&lt;/p&gt;

&lt;p&gt;And yet... Sometimes, one just needs Docker to work. Maybe some tooling you use can't handle Podman, or you just want to put WSL through its paces. If so, read on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make sure that WSL is version 2
&lt;/h2&gt;

&lt;p&gt;Please note that these steps require WSL 2 (not version 1). WSL 2 uses an actual Linux kernel that allows Linux containers. WSL 1 was genius with running Linux on the Windows kernel, but of course lacked some of the features, such as containers. Microsoft offers a &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/compare-versions" rel="noopener noreferrer"&gt;more detailed comparison in the docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You will most certainly need WSL 2 to run the Docker service.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Does the command &lt;code&gt;wsl --set-default-version 2&lt;/code&gt; work? This will set the default version to WSL 2, or fail if you are still on the first version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10#step-2---update-to-wsl-2" rel="noopener noreferrer"&gt;Microsoft's has step-by-step instructions on how to upgrade to WSL 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To run WSL 2, Windows version 1903 or higher is needed, with Build 18362 or higher. To tell what version you are running, run &lt;code&gt;winver&lt;/code&gt; in Powershell or CMD, or just type Win key and R (⊞-r) to open the Run dialog and then enter &lt;code&gt;winver&lt;/code&gt;. Hopefully you will see something like "Version 21H2. OS Build 19044.1586"&lt;/p&gt;

&lt;h2&gt;
  
  
  Install a Linux distro
&lt;/h2&gt;

&lt;p&gt;If you do not yet have a running WSL instance with a distro of your choice, the next step is to &lt;a href="https://aka.ms/wslstore" rel="noopener noreferrer"&gt;pick one from the Microsoft Store&lt;/a&gt;. If you dislike the Windows Store, &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/install-manual#downloading-distros" rel="noopener noreferrer"&gt;there are other options&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Custom installations are also a great option with WSL 2. For instance, &lt;a href="https://dev.to/bowmanjd/install-fedora-on-windows-subsystem-for-linux-wsl-4b26"&gt;install and configure Fedora&lt;/a&gt;, or any other distro for which you can obtain a rootfs in tar format and then &lt;code&gt;wsl --import rootfs.tar&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This guide includes instructions for launching dockerd in &lt;a href="https://www.microsoft.com/en-us/p/debian/9msvkqc78pk6" rel="noopener noreferrer"&gt;Debian&lt;/a&gt;, &lt;a href="https://ubuntu.com/wsl" rel="noopener noreferrer"&gt;Ubuntu&lt;/a&gt;, &lt;a href="https://www.microsoft.com/en-us/p/alpine-wsl/9p804crf0395" rel="noopener noreferrer"&gt;Alpine&lt;/a&gt;, and &lt;a href="https://dev.to/bowmanjd/install-fedora-on-windows-subsystem-for-linux-wsl-4b26"&gt;Fedora&lt;/a&gt;. If you think there is another obvious WSL distro that should be considered, feel free to let me know in the comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure a non-root user
&lt;/h2&gt;

&lt;p&gt;Once you have installed the distro of your choice, launch it and set up a non-root user if you have not already. Debian and Ubuntu will configure this automatically at first launch, as should Alpine if you installed it from the Store. If the &lt;code&gt;whoami&lt;/code&gt; command returnes "root", then you will want to add a non-root user. For Alpine or Fedora, use &lt;code&gt;adduser myusername&lt;/code&gt; to create a new user. On Alpine, this should prompt for the new password. On Fedora, you will additionally need to &lt;code&gt;passwd myusername&lt;/code&gt; and enter the password you want to use. (If your Fedora does not have &lt;code&gt;passwd&lt;/code&gt;, then you will need to first &lt;code&gt;dnf install passwd cracklib-dicts&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;On later versions of Alpine from the Microsoft Store, while a non-root user is created as part of setup, this user is initially password-less. You can double check on any distro with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat /etc/shadow | grep myusername | cut -d: -f2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(If you are not root, you may need to &lt;code&gt;su&lt;/code&gt; first).&lt;/p&gt;

&lt;p&gt;If the result is "!" then that user has no password set. If the result is a random hash string, then you are good. If you need to set a password, you can use &lt;code&gt;passwd myusername&lt;/code&gt; (of course, in all of the above, use your username in place of "myusername."&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure admin (sudo) access for the non-root user
&lt;/h2&gt;

&lt;p&gt;If you used Debian or Ubuntu from the Windows store and set up the default user on first launch, then sudo should already be configured on behalf of the default user. You can skip this step, and proceed to updating packages and testing network connectivity, below.&lt;/p&gt;

&lt;p&gt;When signed in as the user you set up (try &lt;code&gt;su myusername&lt;/code&gt; if you are still root), can you &lt;code&gt;sudo -v&lt;/code&gt; without an error?&lt;/p&gt;

&lt;p&gt;If not, first make sure that sudo is installed. On Alpine, that's &lt;code&gt;apk add sudo&lt;/code&gt; and on Fedora, &lt;code&gt;dnf install sudo&lt;/code&gt;. If this fails due to network connectivity, see below. You can follow the directions there in order to correct DNS, but of course eliminate any occurrence of &lt;code&gt;sudo&lt;/code&gt; in those commands, as you do not have it yet, and you should still be root anyway.&lt;/p&gt;

&lt;p&gt;Is your user a "sudoer"? Try the following to see if they are part of the &lt;code&gt;sudo&lt;/code&gt; or &lt;code&gt;wheel&lt;/code&gt; group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'sudo|wheel'&lt;/span&gt; /etc/group
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On distros that have a &lt;code&gt;sudo&lt;/code&gt; group, such as Ubuntu and Debian, you should see something like &lt;code&gt;sudo:x:27:myusername&lt;/code&gt; and on distros that have a &lt;code&gt;wheel&lt;/code&gt; group, such as Fedora and Alpine, you should see something like &lt;code&gt;wheel:27:myusername&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If your username is missing from the group, take note of the group name (&lt;code&gt;sudo&lt;/code&gt; or &lt;code&gt;wheel&lt;/code&gt;) and add the user in question to that group:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alpine: &lt;code&gt;addgroup myusername wheel&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fedora: &lt;code&gt;usermod -aG wheel myusername&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Probably not necessary, but on Ubuntu/Debian: &lt;code&gt;usermod -aG sudo myusername&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, as root, make sure that the admin group (whether &lt;code&gt;sudo&lt;/code&gt; or &lt;code&gt;wheel&lt;/code&gt;) is enabled for sudo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'%sudo|%wheel'&lt;/span&gt; /etc/sudoers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the line is there, but commented out with a &lt;code&gt;#&lt;/code&gt;, then run &lt;code&gt;visudo&lt;/code&gt; then make sure the line reads thus (use &lt;code&gt;wheel&lt;/code&gt; or &lt;code&gt;sudo&lt;/code&gt; as determined earlier):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%wheel ALL=(ALL) ALL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then save.&lt;/p&gt;

&lt;p&gt;Once these steps are complete, test again with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;su myusername
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are prompted for the password, then all is well. If you instead received an error containing something like "Sorry, user myusername may not run sudo" then you may need to follow the steps again, from the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set default user
&lt;/h2&gt;

&lt;p&gt;If you obtained your Linux distro from the Store, you can likely skip this step, as the default user is already set up.&lt;/p&gt;

&lt;p&gt;If, however, when you launch WSL, you are still root, then set your new user as the default.&lt;/p&gt;

&lt;p&gt;Assuming you have Windows build 18980 or later: simply add a &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/wsl-config#user" rel="noopener noreferrer"&gt;user section to &lt;code&gt;/etc/wsl.conf&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Something like this will work well if you do not already have that file, or a &lt;code&gt;[user]&lt;/code&gt; section in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;[user]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;default = myusername&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/wsl.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, if on a version of Windows before build 18980, then you will instead need to edit the registry to set a default user. Before doing this, we will need two bits of information: the user id, and the name of the WSL distro. Chances are, you already know these. If not, you can obtain the user id with &lt;code&gt;id -u myusername&lt;/code&gt; and check your list of WSL distros with (in Powershell) &lt;code&gt;wsl -l&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then, use the following command in Powershell, but use your WSL distro name in place of "Alpine" and use your user id in place of "1000":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ItemProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Lxss\&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DistributionName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DistributionName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Alpine&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Set-ItemProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DefaultUid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whichever method you use, test by logging out of WSL, and then log back in. Confirm that &lt;code&gt;whoami&lt;/code&gt; yields the correct username. Success.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update/upgrade packages and test network connectivity
&lt;/h2&gt;

&lt;p&gt;Let's make everything new and shiny with one of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debian/Ubuntu: &lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt upgrade&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fedora: &lt;code&gt;sudo dnf upgrade&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Alpine: &lt;code&gt;sudo apk upgrade -U&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Network issues?
&lt;/h2&gt;

&lt;p&gt;Upgrading the packages also serves as a network test. For a variety of reasons, network connectivity issues &lt;a href="https://github.com/microsoft/WSL/issues?q=is%3Aissue+label%3Anetwork" rel="noopener noreferrer"&gt;can happen&lt;/a&gt; with WSL 2, and tweaking the DNS settings often resolves these problems in my experience. If the upgrade command succeeded, you can skip this section. But if the above commands fail to access the package servers, it may be something unique to your network, or your firewall or anti-malware software. I recommend the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"[network]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;generateResolvConf = false"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/wsl.conf
&lt;span class="nb"&gt;sudo unlink&lt;/span&gt; /etc/resolv.conf
&lt;span class="nb"&gt;echo &lt;/span&gt;nameserver 1.1.1.1 | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/resolv.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line tells WSL to cease auto-configuring the &lt;code&gt;/etc/resolv.conf&lt;/code&gt; file. Then we remove/unlink the old file, and create a new one.&lt;/p&gt;

&lt;p&gt;With this newly-configured DNS resolver (in this case, pointing directly to Cloudflare's DNS server) you can try upgrading packages again. Success? Excellent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare for Docker installation
&lt;/h2&gt;

&lt;p&gt;Thankfully, there are &lt;a href="https://docs.docker.com/engine/install/" rel="noopener noreferrer"&gt;official guides for installing Docker&lt;/a&gt; on various Linux distributions. I have based these instructions on those, with some tweaks learned from real world testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remove residue
&lt;/h3&gt;

&lt;p&gt;If this is not a fresh install, and you may have experimented with docker before, then first clear out any residual docker installs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fedora: &lt;code&gt;sudo dnf remove moby-engine docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Debian/Ubuntu: &lt;code&gt;sudo apt remove docker docker-engine docker.io containerd runc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Alpine (probably not necessary, but just in case): &lt;code&gt;sudo apk del docker-cli docker-ce docker-openrc docker-compose docker&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Install dependencies
&lt;/h3&gt;

&lt;p&gt;Then, install pre-requisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debian/Ubuntu: &lt;code&gt;sudo apt install --no-install-recommends apt-transport-https ca-certificates curl gnupg2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fedora: &lt;code&gt;sudo dnf install dnf-plugins-core&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Alpine: Nothing needed. Dependencies will be installed later, automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Debian: switch to legacy iptables
&lt;/h3&gt;

&lt;p&gt;Docker &lt;a href="https://docs.docker.com/network/iptables/" rel="noopener noreferrer"&gt;utilizes iptables to implement network isolation&lt;/a&gt;. For good reason, &lt;a href="https://wiki.debian.org/nftables" rel="noopener noreferrer"&gt;Debian uses the more modern nftables&lt;/a&gt;, but this means that Docker cannot automatically tweak the Linux firewall. Given this, you probably want to configure Debian to use the legacy iptables by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;update-alternatives --config iptables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And select iptables-legacy.&lt;/p&gt;

&lt;p&gt;If you are comfortable, instead, with nftables and want to configure nftables manually for Docker, then go for it. I suspect that most, however, will want to switch to iptables legacy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debian/Ubuntu package repository configuration
&lt;/h3&gt;

&lt;p&gt;On Debian or Ubuntu, first temporarily set some OS-specific variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;.&lt;/span&gt; /etc/os-release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, make sure that &lt;code&gt;apt&lt;/code&gt; will trust the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/gpg | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/trusted.gpg.d/docker.asc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ID&lt;/code&gt; will be either "ubuntu" or "debian", as appropriate, depending on what is in &lt;code&gt;/etc/os-release&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then add and update the repo information so that &lt;code&gt;apt&lt;/code&gt; will use it in the future:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb [arch=amd64] https://download.docker.com/linux/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VERSION_CODENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; stable"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.list
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fedora package repository configuration
&lt;/h3&gt;

&lt;p&gt;On Fedora, first add Docker's repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf config-manager &lt;span class="nt"&gt;--add-repo&lt;/span&gt; https://download.docker.com/linux/fedora/docker-ce.repo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install Docker
&lt;/h2&gt;

&lt;p&gt;Now we can install the official Docker Engine and client tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debian/Ubuntu: &lt;code&gt;sudo apt install docker-ce docker-ce-cli containerd.io&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fedora: &lt;code&gt;sudo dnf install docker-ce docker-ce-cli containerd.io&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Alpine (install the latest from edge): &lt;code&gt;sudo apk add docker --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Add user to &lt;code&gt;docker&lt;/code&gt; group
&lt;/h2&gt;

&lt;p&gt;The Docker daemon is a service that Docker requires to be running in the background. The service (&lt;code&gt;dockerd&lt;/code&gt;) and client (&lt;code&gt;docker&lt;/code&gt;) communicate over a socket and/or a network port. For communication over the socket, privileged access is required. Two ways to obtain this access:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;docker&lt;/code&gt; as root (i.e. &lt;code&gt;sudo docker&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Through group membership, grant specific users privileged access to the Docker socket&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, unless you want to utilize &lt;code&gt;sudo&lt;/code&gt; or root access every time, add your user to the Docker group, named &lt;code&gt;docker&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fedora/Ubuntu/Debian: &lt;code&gt;sudo usermod -aG docker $USER&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Alpine: &lt;code&gt;sudo addgroup $USER docker&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then close that WSL window, and launch WSL again. You should see &lt;code&gt;docker&lt;/code&gt; when you run the command &lt;code&gt;groups&lt;/code&gt; to list group memberships.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing dockerd: choose a common ID for the &lt;code&gt;docker&lt;/code&gt; group
&lt;/h2&gt;

&lt;p&gt;If you only plan on using one WSL distro, this next step isn't strictly necessary. However, if you would like to have the option of sharing the Docker socket system-wide, across WSL distributions, then all will need to share a common group ID for the group &lt;code&gt;docker&lt;/code&gt;. By default, they each may have a different ID, so a new one is in order.&lt;/p&gt;

&lt;p&gt;First, let's pick one. It can be any group ID that is not in use. Choose a number &lt;a href="https://en.wikipedia.org/wiki/Group_identifier#Reserved_ranges" rel="noopener noreferrer"&gt;greater than 1000&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Group_identifier#Type" rel="noopener noreferrer"&gt;less than 65534&lt;/a&gt;. To see what group IDs are already assigned that are 1000 or above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;getent group | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;: &lt;span class="nt"&gt;-f3&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^[0-9]{4}'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can't decide what number to use? May I suggest 36257. (Just dial DOCKR on your telephone keypad...) Not likely to be already in use, but check anyway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;getent group | &lt;span class="nb"&gt;grep &lt;/span&gt;36257 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Yes, that ID is free"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the above command returns a line from &lt;code&gt;/etc/group&lt;/code&gt; (that does not include &lt;code&gt;docker&lt;/code&gt;), then pick another number and try again. If it returns "Yes, that ID is free" then you are good to go, with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s/^\(docker:x\):[^:]\+/\1:36257/'&lt;/span&gt; /etc/group
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if &lt;code&gt;groupmod&lt;/code&gt; is available (which it is on Fedora, Ubuntu, and Debian, but not Alpine unless you &lt;code&gt;sudo apk add shadow&lt;/code&gt;), this is safer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;groupmod &lt;span class="nt"&gt;-g&lt;/span&gt; 36257 docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the group id has been changed, close the terminal window and re-launch your WSL distro.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note that the above steps involving the &lt;code&gt;docker&lt;/code&gt; group will need to be run on any WSL distribution you currently have or install in the future, if you want to give it access to the shared Docker socket.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  (Optionally) prepare a shared directory
&lt;/h2&gt;

&lt;p&gt;As with the last step, if you only plan on using one WSL distro, this next step isn't strictly necessary. However, if you would like to have the option of sharing the Docker socket system-wide, across WSL distributions, then a shared directory accessible to all is needed.&lt;/p&gt;

&lt;p&gt;Let's first make a shared directory for the docker socket, and set permissions so that the docker group can write to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DOCKER_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/mnt/wsl/shared-docker
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-pm&lt;/span&gt; &lt;span class="nv"&gt;o&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;,ug&lt;span class="o"&gt;=&lt;/span&gt;rwx &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;chgrp &lt;/span&gt;docker &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure &lt;code&gt;dockerd&lt;/code&gt; to use the shared directory
&lt;/h2&gt;

&lt;p&gt;Again, this step can be skipped if you opt against using a shared directory for the docker socket. However, you may have other settings you wish to put in &lt;code&gt;daemon.json&lt;/code&gt;, so you may appreciate some familiarity with this topic.&lt;/p&gt;

&lt;p&gt;I suggest using the configuration file &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt; to set dockerd launch parameters. If the &lt;code&gt;/etc/docker&lt;/code&gt; directory does not exist yet, create it with &lt;code&gt;sudo mkdir /etc/docker/&lt;/code&gt; so it can contain the config file. Then the following, when placed in &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;, will set the docker host to the shared socket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hosts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"unix:///mnt/wsl/shared-docker/docker.sock"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Launch &lt;code&gt;dockerd&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Most Linux distributions use systemd or other init system, but WSL has its own init system. Rather than twist things to use the existing init system, we just launch &lt;code&gt;dockerd&lt;/code&gt; directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dockerd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There should be several lines of info, warnings related to &lt;code&gt;cgroup blkio&lt;/code&gt;, and the like, with something like &lt;code&gt;API listen on /mnt/wsl/shared-docker/docker.sock&lt;/code&gt; at the end. If so, you have success.&lt;/p&gt;

&lt;p&gt;Open another wsl terminal.&lt;/p&gt;

&lt;p&gt;If and only if you opted to use the shared docker socket in &lt;code&gt;/mnt/wsl/shared-docker&lt;/code&gt; as detailed above, first set the &lt;code&gt;DOCKER_HOST&lt;/code&gt; environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DOCKER_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"unix:///mnt/wsl/shared-docker/docker.sock"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, try out the docker cli:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the "Hello from Docker!" message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Launch script for &lt;code&gt;dockerd&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The following lines can be placed in &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.profile&lt;/code&gt; if autolaunching is desired, or in a separate shell script. For instance, you may want to create a script &lt;code&gt;~/bin/docker-service&lt;/code&gt; so that you can run &lt;code&gt;docker-service&lt;/code&gt; only when you want, manually.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DOCKER_DISTRO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Debian"&lt;/span&gt;
&lt;span class="nv"&gt;DOCKER_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/mnt/wsl/shared-docker
&lt;span class="nv"&gt;DOCKER_SOCK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_DIR&lt;/span&gt;&lt;span class="s2"&gt;/docker.sock"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DOCKER_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"unix://&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_SOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_SOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-pm&lt;/span&gt; &lt;span class="nv"&gt;o&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;,ug&lt;span class="o"&gt;=&lt;/span&gt;rwx &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;chgrp &lt;/span&gt;docker &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    /mnt/c/Windows/System32/wsl.exe &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;$DOCKER_DISTRO&lt;/span&gt; sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"nohup sudo -b dockerd &amp;lt; /dev/null &amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_DIR&lt;/span&gt;&lt;span class="s2"&gt;/dockerd.log 2&amp;gt;&amp;amp;1"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you went with the default docker socket location of &lt;code&gt;/var/run/docker.sock&lt;/code&gt; instead of the shared socket directory of &lt;code&gt;/mnt/wsl/shared-docker&lt;/code&gt; as detailed above, then the script can be something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DOCKER_DISTRO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Debian"&lt;/span&gt;
&lt;span class="nv"&gt;DOCKER_LOG_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/docker_logs
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-pm&lt;/span&gt; &lt;span class="nv"&gt;o&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;,ug&lt;span class="o"&gt;=&lt;/span&gt;rwx &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_LOG_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
/mnt/c/Windows/System32/wsl.exe &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;$DOCKER_DISTRO&lt;/span&gt; sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"nohup sudo -b dockerd &amp;lt; /dev/null &amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_LOG_DIR&lt;/span&gt;&lt;span class="s2"&gt;/dockerd.log 2&amp;gt;&amp;amp;1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may choose whatever location you would like for your docker logs, of course. It just needs to be in a place that has permissions so that your user can write to it.&lt;/p&gt;

&lt;p&gt;Note that &lt;code&gt;DOCKER_DISTRO&lt;/code&gt; should be set to the distro you want to have running &lt;code&gt;dockerd&lt;/code&gt;. If unsure of the name, simply run &lt;code&gt;wsl -l -q&lt;/code&gt; from Powershell to see your list of WSL distributions. Pick the right one and set it to &lt;code&gt;DOCKER_DISTRO&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The script above does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sets the environment variable &lt;code&gt;$DOCKER_HOST&lt;/code&gt; to point to the shared socket. This isn't necessary for &lt;code&gt;dockerd&lt;/code&gt; but it will allow running &lt;code&gt;docker&lt;/code&gt; without needing to specify &lt;code&gt;-H unix:///mnt/wsl/shared-docker/docker.sock&lt;/code&gt; each time.&lt;/li&gt;
&lt;li&gt;Checks if the &lt;code&gt;docker.sock&lt;/code&gt; file already exists and is a socket. If it is present, do nothing. If not present then the script does the remaining steps, as follows.&lt;/li&gt;
&lt;li&gt;Creates the shared docker directory for the socket and &lt;code&gt;dockerd&lt;/code&gt; logs, setting permissions appropriately&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;dockerd&lt;/code&gt; &lt;em&gt;from the specified distro&lt;/em&gt;. This is important, because it allows any WSL distro to launch &lt;code&gt;dockerd&lt;/code&gt; if it isn't already running.&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;dockerd&lt;/code&gt; is launched, pipe its output and errors to a shared log file.&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;dockerd&lt;/code&gt; is launched, it runs in the background so you don't have to devote a terminal window to the process as we did earlier. The &lt;code&gt;sudo -b&lt;/code&gt; flag gives us this, and we run with &lt;code&gt;nohup&lt;/code&gt; so that it runs independent of the terminal, with an explicit null input to &lt;code&gt;nohup&lt;/code&gt; to avoid extra warnings. Both standard output and errors are written to the logfile, hence the &lt;code&gt;2&amp;gt;&amp;amp;1&lt;/code&gt; redirect.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Passwordless launch of &lt;code&gt;dockerd&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;If the above script is placed in &lt;code&gt;.bashrc&lt;/code&gt; (most Linux distros) or &lt;code&gt;.profile&lt;/code&gt; (distros like Alpine that have Ash/Dash as the default shell), or other shell init script, then it has an unfortunate side effect: you will likely be prompted for a password most every time a new terminal window is launched.&lt;/p&gt;

&lt;p&gt;To work around this, you can, if you choose, tell &lt;code&gt;sudo&lt;/code&gt; to grant passwordless access to &lt;code&gt;dockerd&lt;/code&gt;, as long as the user is a member of the &lt;code&gt;docker&lt;/code&gt; group. To do so, enter &lt;code&gt;sudo visudo&lt;/code&gt; and add the following line (if your &lt;code&gt;visudo&lt;/code&gt; uses &lt;code&gt;vi&lt;/code&gt; or &lt;code&gt;vim&lt;/code&gt;, then be sure to press "&lt;code&gt;i&lt;/code&gt;" to begin editing, and hit ESC when done editing):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;%docker &lt;span class="nv"&gt;ALL&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;ALL&lt;span class="o"&gt;)&lt;/span&gt;  NOPASSWD: /usr/bin/dockerd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and exit ("&lt;code&gt;:wq&lt;/code&gt;" if the editor is &lt;code&gt;vi&lt;/code&gt;, or Ctrl-x if it is &lt;code&gt;nano&lt;/code&gt;), and then you can test if &lt;code&gt;sudo dockerd&lt;/code&gt; prompts for a password or not. For peace of mind, you can double-check: something like &lt;code&gt;sudo -k ls -a /root&lt;/code&gt; should still require a password, unless the password has been entered recently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make sure &lt;code&gt;$DOCKER_HOST&lt;/code&gt; is always set
&lt;/h2&gt;

&lt;p&gt;If using the script earlier to launch &lt;code&gt;dockerd&lt;/code&gt;, then &lt;code&gt;$DOCKER_HOST&lt;/code&gt; will be set, and future invocations of &lt;code&gt;docker&lt;/code&gt; will not need an unwieldy &lt;code&gt;-H unix:///mnt/wsl/shared-docker/docker.sock&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If that script is already in your &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.profile&lt;/code&gt;, then the following is unnecessary. If, however, you manually invoke &lt;code&gt;dockerd&lt;/code&gt; in some way, then the following may be desirable in your &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.profile&lt;/code&gt;, if you opted for the shared docker socket directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DOCKER_SOCK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/mnt/wsl/shared-docker/docker.sock"&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_SOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DOCKER_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"unix://&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_SOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above checks for the docker socket in &lt;code&gt;/mnt/wsl/shared-docker/docker.sock&lt;/code&gt; and, if present, sets the &lt;code&gt;$DOCKER_HOST&lt;/code&gt; environment variable accordingly. If you want a more generalized "if this is wsl, then set the socket pro-actively" then you may prefer the following, which simply check for the existence of a &lt;code&gt;/mnt/wsl&lt;/code&gt; directory and sets the docker socket if so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; /mnt/wsl &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DOCKER_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"unix:///mnt/wsl/shared-docker/docker.sock"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running &lt;code&gt;docker&lt;/code&gt; from Windows
&lt;/h2&gt;

&lt;p&gt;If configured as above, I recommend always running docker from wsl. Do you want to run a container? Do so from a WSL window. (&lt;a href="https://dev.to/bowmanjd/use-the-new-windows-terminal-4ll7"&gt;See my article on using Windows Terminal&lt;/a&gt; for a convenient way to use WSL and Powershell.)&lt;/p&gt;

&lt;p&gt;This doesn't just apply to the terminal, either. For instance, &lt;a href="https://code.visualstudio.com/blogs/2020/07/01/containers-wsl" rel="noopener noreferrer"&gt;VSCode supports docker in WSL 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But if you want the convenience and utility of running docker in a Powershell window, I have a couple suggestions.&lt;/p&gt;

&lt;p&gt;One is to expose &lt;code&gt;dockerd&lt;/code&gt; over a TCP Port, or, better yet, set up an SSH server in WSL and connect that way. &lt;code&gt;docker context&lt;/code&gt; will likely be your friend. Such methods will be explored in a later article, but I encourage you, reader, to explore. A hint: ever tried &lt;a href="https://scoop.sh/" rel="noopener noreferrer"&gt;scoop.sh&lt;/a&gt;? After setting it up, &lt;code&gt;scoop install docker docker-compose&lt;/code&gt; will get you some familiar tools, then an SSH server such as Dropbear or OpenSSH on the WSL side...&lt;/p&gt;

&lt;p&gt;A simplified method I recommend: a Powershell function that calls the WSL &lt;code&gt;docker&lt;/code&gt;, passing along any arguments. This function can be placed in your Powershell profile, usually located at &lt;code&gt;~\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$DOCKER_DISTRO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fedora"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_DISTRO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-H&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unix:///mnt/wsl/shared-docker/docker.sock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, try &lt;code&gt;wsl -l -q&lt;/code&gt; to see a list of your WSL distributions if you are unsure which one to use.&lt;/p&gt;

&lt;p&gt;Make sure the Docker daemon is running, then launch a new Powershell window, and try the &lt;code&gt;hello-world&lt;/code&gt; container again. Success?&lt;/p&gt;

&lt;p&gt;You could also make a batch file with the appropriate command in it. For instance, name it &lt;code&gt;docker.bat&lt;/code&gt; and place in &lt;code&gt;C:\Windows\system32&lt;/code&gt; or other location included in &lt;code&gt;%PATH%&lt;/code&gt;. The following contents will work in such a script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;@echo &lt;span class="na"&gt;off&lt;/span&gt;
&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="kd"&gt;DOCKER_DISTRO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kd"&gt;fedora&lt;/span&gt;
&lt;span class="kd"&gt;wsl&lt;/span&gt; &lt;span class="na"&gt;-d &lt;/span&gt;&lt;span class="nv"&gt;%DOCKER_DISTRO%&lt;/span&gt; &lt;span class="kd"&gt;docker&lt;/span&gt; &lt;span class="na"&gt;-H &lt;/span&gt;&lt;span class="kd"&gt;unix&lt;/span&gt;:///mnt/wsl/shared&lt;span class="na"&gt;-docker/docker&lt;/span&gt;.sock &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could go a step further and ensure that &lt;code&gt;dockerd&lt;/code&gt; is running whenever you start Powershell. Assuming that the &lt;code&gt;dockerd&lt;/code&gt; start script detailed above is saved in a file in WSL as &lt;code&gt;$HOME/bin/docker-service&lt;/code&gt; and is executable (try &lt;code&gt;chmod a+x $HOME/bin/docker-service&lt;/code&gt;), then the following line in your Powershell profile will launch &lt;code&gt;dockerd&lt;/code&gt; automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$distro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;~/bin/docker-service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not sure where your Powershell profile is located? Try entering &lt;code&gt;$profile&lt;/code&gt; in a powershell window.&lt;/p&gt;

&lt;p&gt;If you don't want to rely on a particular WSL shell script, you could implement a Powershell function to launch dockerd, such as this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Docker-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kr"&gt;Param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$distro&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_DIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/mnt/wsl/shared-docker"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_SOCK&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_DIR&lt;/span&gt;&lt;span class="s2"&gt;/docker.sock"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$distro&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[ -S '&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_SOCK&lt;/span&gt;&lt;span class="s2"&gt;' ]"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$LASTEXITCODE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$distro&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mkdir -pm o=,ug=rwx &lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_DIR&lt;/span&gt;&lt;span class="s2"&gt; ; chgrp docker &lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$distro&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nohup sudo -b dockerd &amp;lt; /dev/null &amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_DIR&lt;/span&gt;&lt;span class="s2"&gt;/dockerd.log 2&amp;gt;&amp;amp;1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function takes one parameter: the distro name.&lt;/p&gt;

&lt;p&gt;In all of the above, the principle is the same: you are launching Linux executables, using &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/interop" rel="noopener noreferrer"&gt;WSL interoperability&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on bind mounts: stay on the Linux filesystem
&lt;/h2&gt;

&lt;p&gt;With docker, it is possible to mount a host system's directory or files in the container. The following often works, but is &lt;em&gt;not&lt;/em&gt; advisable when launching WSL docker from Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;h1&amp;gt;Hello, World&amp;lt;/h1&amp;gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; index.html
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"8080:80"&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;:/usr/share/nginx/html:ro"&lt;/span&gt; nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of doing the above haphazardly, when launching WSL docker from Powershell, two recommendations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For performance reasons, only bind mount from within the Linux filesystem. To get to a Linux directory while in Powershell, try something like &lt;code&gt;cd (wsl wslpath -m ~/path/to/my/dir)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Instead of &lt;code&gt;$PWD&lt;/code&gt; to get the current directory, try &lt;code&gt;'$(wslpath -a .)'&lt;/code&gt; and, yeah, the single quotes are helpful for escaping.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wslpath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;~&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;h1&amp;gt;Hello, World&amp;lt;/h1&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;index.html&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8080:80"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'$(wslpath -a .):/usr/share/nginx/html:ro'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nginx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point your browser to &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;, and happiness will result. (Depending on &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/compare-versions#accessing-a-wsl-2-distribution-from-your-local-area-network-lan" rel="noopener noreferrer"&gt;your network configuration&lt;/a&gt;, you may instead need to access this through http://[WSL IP Address]:8080 which should be obtainable with &lt;code&gt;ifconfig&lt;/code&gt; or &lt;code&gt;ip addr&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Try &lt;code&gt;wsl wslpath&lt;/code&gt; from Powershell, or just &lt;code&gt;wslpath&lt;/code&gt; from Linux, to see the options. This is a very useful tool, to say the least.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example scripts
&lt;/h2&gt;

&lt;p&gt;After walking through the steps in this article, you should now have a working and potentially auto-launched &lt;code&gt;dockerd&lt;/code&gt;, shared Docker socket, and conveniently configured &lt;code&gt;docker&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Some of the code examples above have been placed in scripts in &lt;a href="https://github.com/bowmanjd/docker-wsl" rel="noopener noreferrer"&gt;a companion Github repo&lt;/a&gt;. I summarize the files available here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/bowmanjd/docker-wsl/blob/main/docker-service.sh" rel="noopener noreferrer"&gt;docker-service.sh&lt;/a&gt; is a Unix shell script to launch &lt;code&gt;dockerd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bowmanjd/docker-wsl/blob/main/docker-service.ps1" rel="noopener noreferrer"&gt;docker-service.ps1&lt;/a&gt; contains a Powershell function to launch &lt;code&gt;dockerd&lt;/code&gt; in WSL&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bowmanjd/docker-wsl/blob/main/docker.bat" rel="noopener noreferrer"&gt;docker.bat&lt;/a&gt; is a Windows batch file for launching WSL &lt;code&gt;docker&lt;/code&gt; from CMD&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bowmanjd/docker-wsl/blob/main/docker.ps1" rel="noopener noreferrer"&gt;docker.ps1&lt;/a&gt; contains a Powershell function for launching WSL &lt;code&gt;docker&lt;/code&gt; from Powershell, if placed in your profile&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No doubt there are ways these can be tweaked to be more useful and reliable; feel free to post in the comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interested in further tinkering with WSL 2?
&lt;/h2&gt;

&lt;p&gt;Other articles I have written on WSL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/bowmanjd/using-podman-on-windows-subsystem-for-linux-wsl-58ji"&gt;Using podman instead of Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/bowmanjd/you-probably-don-t-need-systemd-on-wsl-windows-subsystem-for-linux-49gn"&gt;You Probably Don't Need systemd on WSL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/bowmanjd/install-fedora-on-windows-subsystem-for-linux-wsl-4b26"&gt;Install Fedora on WSL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/bowmanjd/how-to-upgrade-from-fedora-32-to-fedora-33-on-windows-subsystem-for-linux-wsl-144e"&gt;How to Upgrade from Fedora 32 to Fedora 33&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>wsl</category>
      <category>windows</category>
    </item>
    <item>
      <title>Bash Execution Tips for Shell Jockeys and Script Fabricators</title>
      <dc:creator>Jonathan Bowman</dc:creator>
      <pubDate>Tue, 09 Feb 2021 18:24:20 +0000</pubDate>
      <link>https://dev.to/bowmanjd/bash-execution-tips-for-shell-jockeys-and-script-fabricators-5dan</link>
      <guid>https://dev.to/bowmanjd/bash-execution-tips-for-shell-jockeys-and-script-fabricators-5dan</guid>
      <description>&lt;p&gt;Have you ever wanted to execute one command if another failed? Or one command only if the first one succeeded? What about background execution?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gnu.org/software/bash/"&gt;Bash&lt;/a&gt; and other popular &lt;a href="https://en.wikipedia.org/wiki/Shell_%28computing%29"&gt;shells&lt;/a&gt; such as &lt;a href="http://zsh.sourceforge.net/"&gt;Zsh&lt;/a&gt; and &lt;a href="http://gondor.apana.org.au/~herbert/dash/"&gt;Ash/Dash&lt;/a&gt; have some useful but sometimes confusing operators for command execution.&lt;/p&gt;

&lt;p&gt;What is the difference between &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;||&lt;/code&gt; and &lt;code&gt;;&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;A very brief cheatsheet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; to execute one command only when the previous one succeeds.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;||&lt;/code&gt; to execute one command only when the previous one fails.&lt;/li&gt;
&lt;li&gt;Combine the above for conditional branching.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;;&lt;/code&gt; to join two commands when you want the second to execute &lt;em&gt;no matter&lt;/em&gt; the result of the first one.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;&amp;amp;&lt;/code&gt; to run the first job in the background while the next executes. Follow all with &lt;code&gt;wait&lt;/code&gt; for a clean return to the command prompt&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Execute if previous command succeeds: &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;When running one command, sometimes it is desirable for another command to execute if and only if the first one was successful.&lt;/p&gt;

&lt;p&gt;Let's say we want to create a user's home directory if and only if that user already exists. We can test for the user's existence with &lt;code&gt;id&lt;/code&gt;, then, if successful, create the home directory, then, if &lt;em&gt;that&lt;/em&gt; is successful, write a &lt;code&gt;.bashrc&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;id &lt;/span&gt;fredflintstone &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; /home/fredflintstone &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'echo "Welcome, $USER"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /home/fredflintstone/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the use of the &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, a logical "and", for conditional execution. If &lt;em&gt;this&lt;/em&gt;, then &lt;em&gt;that&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; operator should not be confused with the background execution operator &lt;code&gt;&amp;amp;&lt;/code&gt; noted below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Execute if previous command fails: &lt;code&gt;||&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Sometimes, the opposite of the above is desired: execute the next command if the previous one &lt;em&gt;failed&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;For instance, perhaps we want to add a DNS server to &lt;code&gt;/etc/resolv.conf&lt;/code&gt; only if it doesn't already exist. We can use grep to test if the server name already exists, then write back to the file if it wasn't already there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep &lt;/span&gt;1.1.1.1 /etc/resolv.conf &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"nameserver 1.1.1.1"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/resolv.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the use of the &lt;code&gt;||&lt;/code&gt;, a logical "or", for conditional execution. If not &lt;em&gt;this&lt;/em&gt;, then &lt;em&gt;that&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Also note the pipe &lt;code&gt;|&lt;/code&gt; operator. It should not be confused with the &lt;code&gt;||&lt;/code&gt; operator. The pipe &lt;code&gt;|&lt;/code&gt; means "send the output of this command to the input of the next." In this case, the &lt;code&gt;echo&lt;/code&gt; command pipes output to the &lt;code&gt;tee&lt;/code&gt; command, which appends (due to the &lt;code&gt;-a&lt;/code&gt; option) the input to &lt;code&gt;/etc/resolv.conf&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Branching logic with both &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A pleasant combination of the above is indeed possible. Imagine that you want to execute a command, then, if it succeeds, execute one command, but if it fails, execute a different command. Some creative chaining is possible, as in this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;id &lt;/span&gt;fredflintstone &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /home/fredflintstone &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;id &lt;/span&gt;bettyrubble &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; /home/bettyrubble
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above, if one user does not exist, another will be tried. However, it should be pointed out that this is not equivalent to an if/then/else statement. If the 2nd command &lt;code&gt;mkdir -p /home/fredflintsone&lt;/code&gt; would fail (not likely, with the &lt;code&gt;-p&lt;/code&gt; flag), &lt;code&gt;id bettyrubble&lt;/code&gt; would still run. In other words, when executing &lt;code&gt;true &amp;amp;&amp;amp; false || true&lt;/code&gt; all three commands run, because the failure of the 2nd command triggers the 3rd.&lt;/p&gt;

&lt;p&gt;Sometimes, I use this method to explicitly set human-readable variables in a script, so that later readers (i.e. me) will easily understand what is going on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;passwd &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;PWD_IS_SET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;PWD_IS_SET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In effect, the above "caches" a test so that the script can later test for the value of &lt;code&gt;$PWD_IS_SET&lt;/code&gt; numerous times, in a memorable and readable way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unconditional execution with &lt;code&gt;;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To mash a couple commands together in one line, use the semicolon. In human language, a semicolon says, "Do this, wait until it completes, then do that." For instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;Hello &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;World
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The execution is &lt;em&gt;unconditional&lt;/em&gt;; no matter what the first command does, the second will also execute afterward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;filename_that_does_not_exist &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;Continue anyway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Background execution with &lt;code&gt;&amp;amp;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Try this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;Hello &amp;amp; &lt;span class="nb"&gt;echo &lt;/span&gt;World
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, both commands executed, but the behavior may well be quite different than the use of &lt;code&gt;;&lt;/code&gt; above. The above two commands were executed in &lt;em&gt;parallel&lt;/em&gt;. In other words, at the same time. There is no guarantee regarding which will complete first. In fact using just &lt;code&gt;&amp;amp;&lt;/code&gt; at the end of a long running command will allow it to run in the background indefinitely.&lt;/p&gt;

&lt;p&gt;The main point in the context of this article: &lt;code&gt;&amp;amp;&lt;/code&gt; is not &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; nor is it &lt;code&gt;;&lt;/code&gt; (the order and conditions for execution are different for each).&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing teaser
&lt;/h2&gt;

&lt;p&gt;A thorough exploration of the builtin shell command &lt;code&gt;test&lt;/code&gt; is better left for a future article. That said, let's at least dabble a bit, as the concept is quite applicable to conditional execution.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: in &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/"&gt;POSIX&lt;/a&gt;-derived shells such as Bash, Zsh, and Ash/Dash, the &lt;code&gt;test&lt;/code&gt; command and the &lt;code&gt;[&lt;/code&gt; command are the same command, with the exception that when &lt;code&gt;[&lt;/code&gt; is used, it should end with a &lt;code&gt;]&lt;/code&gt;. While &lt;code&gt;[&lt;/code&gt; works well in a pattern like "&lt;code&gt;if [ -d some_directory ]; then&lt;/code&gt;" for multi-line readability (the last line should be "&lt;code&gt;fi&lt;/code&gt;" to end the if statement), for succinct one-liners I prefer "&lt;code&gt;test -d some_directory&lt;/code&gt;" and the like.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A quick cheatsheet with some commonly used tests using the &lt;code&gt;test&lt;/code&gt; command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;test -d some_directory&lt;/code&gt; will be true if a directory exists&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test -r some_file&lt;/code&gt; will be true if a regular file is readable&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test -n "$SOME_STRING"&lt;/code&gt; will be true if a string (such as a variable) is non-empty&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test -z "$SOME_NONEXISTENT_STRING"&lt;/code&gt; will be true if a string is empty&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html"&gt;the POSIX spec for test&lt;/a&gt; for many more options. You might also browse &lt;a href="https://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html#Bash-Conditional-Expressions"&gt;Bash Conditional Expressions&lt;/a&gt; or &lt;a href="http://zsh.sourceforge.net/Doc/Release/Conditional-Expressions.html"&gt;Zsh Conditional Expressions&lt;/a&gt; for shell-specific docs. When possible, I try to write shell scripts in POSIX-compliant ways for portability (scripts that work across a variety of shells). That said, sometimes you may prefer to use the full power of your shell's specific features. Browse the relevant docs to consider your options.&lt;/p&gt;

&lt;p&gt;The above can be very useful for conditional execution. Something like this works well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; /etc/resolv.conf &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"nameserver 1.1.1.1"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/resolv.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, if &lt;code&gt;/etc/resolv.conf&lt;/code&gt; does not exist, create it with the appropriate contents. But if it already exists, do nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The possibility of idempotence (alternate title: put a little Dev in your Ops)
&lt;/h2&gt;

&lt;p&gt;Perhaps you can see a clear use case here for system configuration. If you have ever used &lt;a href="https://www.ansible.com/"&gt;Ansible&lt;/a&gt; and similar configuration tools, you may note that it is desirable for every task to be &lt;a href="https://en.wikipedia.org/wiki/Idempotence"&gt;&lt;em&gt;idempotent&lt;/em&gt;&lt;/a&gt;; in other words, even when run more than once, the desired result is the same.&lt;/p&gt;

&lt;p&gt;I believe it is good for shell scripts to be as idempotent as possible, when they are intended to configure a system in a certain state. The above examples that reference &lt;code&gt;/etc/resolv.conf&lt;/code&gt; do this well, so I will reference them again here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /etc/resolv.conf &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"nameserver 1.1.1.1"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/resolv.conf
&lt;span class="nb"&gt;grep &lt;/span&gt;1.1.1.1 /etc/resolv.conf &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"nameserver 1.1.1.1"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/resolv.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These two lines are very similar, and only one is needed, depending on the desired outcome. The first is a great example of creating a new file with the desired initial state. It uses &lt;code&gt;test -f&lt;/code&gt; to first determine if the file exists. Sure, you could always overwrite the file and ensure state that way, but that would update the file stats, such as timestamp, needlessly. In addition, this example may be useful on configuration files with a desired &lt;em&gt;initial&lt;/em&gt; state, when future modifications are expected and should not be altered.&lt;/p&gt;

&lt;p&gt;The second is instead an example of ensuring that a certain &lt;em&gt;line&lt;/em&gt; exists in a file. It uses &lt;code&gt;grep&lt;/code&gt; to test for a word or line in the file, then adds the line if and only if it isn't already there.&lt;/p&gt;

&lt;p&gt;These methods achieve &lt;a href="https://en.wikipedia.org/wiki/Idempotence"&gt;idempotency&lt;/a&gt; with the combination of testing and conditional execution. A worthy goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  One-liners and robust scripts
&lt;/h2&gt;

&lt;p&gt;The examples and concepts in this article are equally at home in a terminal window or in a substantial configuration script. Shell execution logic should be your friend. Feel free to post examples and tips in the comments below.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>devops</category>
      <category>bash</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Dotfiles the easy way</title>
      <dc:creator>Jonathan Bowman</dc:creator>
      <pubDate>Sun, 07 Feb 2021 13:42:21 +0000</pubDate>
      <link>https://dev.to/bowmanjd/dotfiles-the-easy-way-3iio</link>
      <guid>https://dev.to/bowmanjd/dotfiles-the-easy-way-3iio</guid>
      <description>&lt;p&gt;In my ongoing quest to &lt;a href="https://www.bowmanjd.com/dotfiles/"&gt;explore a variety of ways of managing config files&lt;/a&gt;, I believe I have found a way that is attractively simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step one: git clone
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~
git clone &lt;span class="nt"&gt;-c&lt;/span&gt; status.showUntrackedFiles&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;--separate-git-dir&lt;/span&gt; .git &lt;span class="nv"&gt;$REPO_URL&lt;/span&gt; tmpdir
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; tmpdir
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rationale:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;status.showUntrackedFiles&lt;/code&gt; is set to "no" so that future &lt;code&gt;git status&lt;/code&gt; requests only show files that were intentionally tracked with &lt;code&gt;git add&lt;/code&gt; and &lt;code&gt;git commit&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-n&lt;/code&gt; means no checkout. We aren't ready for it yet.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--separate-git-dir .git&lt;/code&gt; takes some explanation. It is impossible to &lt;code&gt;git clone&lt;/code&gt; into a non-empty directory without some extra steps. This trick does it in one step (two if you count the deletion of the throwaway directory). Tell Git to use a separate Git directory but then, sneaky miscreants that we are, we name the directory the default: &lt;code&gt;.git&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The undesirable side effect is an extra directory &lt;code&gt;tmpdir&lt;/code&gt; that has a single &lt;code&gt;.git&lt;/code&gt; file of no consequence. The entire directory can safely be removed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step two for non-empty repo: git checkout
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deal with any file conflicts. For instance, you might backup and remove an existing &lt;code&gt;.bashrc&lt;/code&gt;. Then run &lt;code&gt;git checkout&lt;/code&gt; again. If you are sure that overwriting is OK, you can pass the force flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step two if new (empty) repo: add files and push
&lt;/h2&gt;

&lt;p&gt;If this is a brand new setup, your repo is likely empty. Add some files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add .bashrc
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"initial commit of Bash config"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repeat as necessary with additional files and directories. Then push:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step three: maintain
&lt;/h2&gt;

&lt;p&gt;Keep your files up to date with &lt;code&gt;git add&lt;/code&gt;, &lt;code&gt;git commit&lt;/code&gt;, &lt;code&gt;git push&lt;/code&gt;. Pull remote changes with &lt;code&gt;git pull&lt;/code&gt;. In other words, manage your home directory as you would any other Git repo. Please avoid &lt;code&gt;git add .&lt;/code&gt; as that will track every file in your home directory, an undesirable endeavor.&lt;/p&gt;

&lt;p&gt;If this spawns other creative ideas or optimizations, feel free to post in the comments!&lt;/p&gt;

</description>
      <category>github</category>
      <category>git</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Using Multiple Git Repositories to Store Dotfiles in a Modular Fashion</title>
      <dc:creator>Jonathan Bowman</dc:creator>
      <pubDate>Thu, 04 Feb 2021 12:22:54 +0000</pubDate>
      <link>https://dev.to/bowmanjd/using-multiple-git-repositories-to-store-dotfiles-in-a-modular-fashion-mni</link>
      <guid>https://dev.to/bowmanjd/using-multiple-git-repositories-to-store-dotfiles-in-a-modular-fashion-mni</guid>
      <description>&lt;p&gt;In this article, I offer an approach for managing dotfiles in a modular way. I find a modular approach important because only some config files are useful in all contexts, while others are unique to a specific environment. For instance, my text editor configuration (&lt;code&gt;.vimrc&lt;/code&gt;, in my case) is used on my Windows laptop, Linux laptop, FreeBSD server, and even my phone. On the other hand, files for configuring a Linux graphical environment, a developer's Macbook, Windows Subsystem for Linux (WSL), or Windows Powershell, may not make sense to clutter or confuse environments to which they do not apply.&lt;/p&gt;

&lt;p&gt;It would be nice to use multiple Git repositories, or multiple branches of one Git repository, in order to customize various environments.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://www.bowmanjd.com/dotfiles/dotfiles-1-simple-no-bare-repo/"&gt;a previous article, I outlined a simple approach to storing dotfiles&lt;/a&gt; that makes the entire home directory a git repository.&lt;/p&gt;

&lt;p&gt;Let's build on that approach, exploring the possibility of using multiple repositories in a modular fashion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary steps
&lt;/h2&gt;

&lt;p&gt;Feel free to read the full article below for detailed explanation and options. As a quick summary, the following steps should get you started with three "modules": &lt;code&gt;base&lt;/code&gt;, &lt;code&gt;personal&lt;/code&gt;, and &lt;code&gt;work&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.bowmanjd.com/dotfiles/dotfiles-1-simple-no-bare-repo/"&gt;Set up the base repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Create two additional directories, such as &lt;code&gt;~/.config/custom/personal&lt;/code&gt; and &lt;code&gt;~/.config/custom/work&lt;/code&gt; and initialize a git repo in each, similar to the base instructions but using those directories instead of home.&lt;/li&gt;
&lt;li&gt;Modularize your config files (see below) so that the main config includes related files in subdirectories of &lt;code&gt;~/.config/custom&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Manage the files: &lt;code&gt;cd&lt;/code&gt; into each module directory and add, commit, push and pull as necessary&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Repository setup
&lt;/h2&gt;

&lt;p&gt;You can continue to use the convenience functions from &lt;a href="https://www.bowmanjd.com/dotfiles/dotfiles-1-simple-no-bare-repo/"&gt;the first article&lt;/a&gt; (&lt;code&gt;dtfnew&lt;/code&gt; and &lt;code&gt;dtfrestore&lt;/code&gt;), just make sure you position yourself in the appropriate directory first, and specify the correct repo for each module. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~
dtfrestore &lt;span class="nv"&gt;$BASEREPO&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/.config/custom
git clone &lt;span class="nv"&gt;$WORKREPO&lt;/span&gt; ~/.config/custom/work
git clone &lt;span class="nv"&gt;$LAPTOPREPO&lt;/span&gt; ~/.config/custom/laptop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above, we first create a base repository in the home directory. This is the repo in which are stored the main files like &lt;code&gt;.bashrc&lt;/code&gt;, &lt;code&gt;.profile&lt;/code&gt;, etc. As noted below, these files should be configured to load other files in other directories.&lt;/p&gt;

&lt;p&gt;Then we clone the remote repositories to the given directories after creating them.&lt;/p&gt;

&lt;p&gt;You can browse &lt;a href="https://github.com/bowmanjd/dotfile-scripts/"&gt;the companion Github repo&lt;/a&gt; for the basic functions used above, in both a &lt;a href="https://github.com/bowmanjd/dotfile-scripts/blob/main/basic.sh"&gt;Unix shell version&lt;/a&gt; and a &lt;a href="https://github.com/bowmanjd/dotfile-scripts/blob/main/basic.ps1"&gt;Powershell version&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One directory per "module" with a common parent directory
&lt;/h2&gt;

&lt;p&gt;Choose a parent directory in which you will place each module directory. I use the term "module" here to refer to a repository that adds additional config files. I do &lt;em&gt;not&lt;/em&gt; mean to refer to &lt;a href="https://git-scm.com/docs/gitsubmodules"&gt;git submodules&lt;/a&gt;. Although that introduces possibilities worth exploring another day...&lt;/p&gt;

&lt;p&gt;I use &lt;code&gt;~/.config/custom&lt;/code&gt; as the parent directory, but you can use any location that serves you well.&lt;/p&gt;

&lt;p&gt;Underneath that parent directory, create a directory for each "module." The end result may look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.config/custom/
├── base
├── macbook
├── personal
├── server
├── work
└── wsl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perhaps you don't need that many, but you get the idea.&lt;/p&gt;

&lt;p&gt;Within each directory, you can place relevant config files. I suggest some advance planning to determine naming scheme, as follows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modularize your config files
&lt;/h2&gt;

&lt;p&gt;An important strategy with a modular approach is to compartmentalize. Rather than imagining a single config file that changes per system, use that file to load other config files &lt;em&gt;if they are present&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Here are some ideas, specific to various tools:&lt;/p&gt;

&lt;h3&gt;
  
  
  Unix shell configs
&lt;/h3&gt;

&lt;p&gt;Shell configurations like &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.zsheenv&lt;/code&gt; or &lt;code&gt;.profile&lt;/code&gt; can source files from other directories.&lt;/p&gt;

&lt;p&gt;For instance, in &lt;code&gt;.bashrc&lt;/code&gt; we might place something like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;file &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.config/custom &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'bashrc'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would load any or all of the following files if they exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.config/custom/work/bashrc
~/.config/custom/personal/bashrc
~/.config/custom/wsl/bashrc
~/.config/custom/linuxui/bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SSH configs
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;.ssh/config&lt;/code&gt;, &lt;a href="https://man.openbsd.org/ssh_config#Include"&gt;the &lt;code&gt;Include&lt;/code&gt; keyword&lt;/a&gt; can be used like so&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Include ~/.config/custom/*/*.ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will include any files ending in &lt;code&gt;.ssh&lt;/code&gt; (such as &lt;code&gt;config.ssh&lt;/code&gt;) in any subdirectory of &lt;code&gt;~/.config/custom&lt;/code&gt;. For example, this will include any of the following files, if they are available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.config/custom/work/config.ssh
~/.config/custom/personal/personal.ssh
~/.config/custom/server/hosts.ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And so on. Name the directories in ways that suit you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Powershell
&lt;/h3&gt;

&lt;p&gt;In Windows, the Powershell profile in &lt;code&gt;~\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1&lt;/code&gt; is automatically loaded. Within that file, you could loop through files in a directory of your choice and load them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;\.config\dotfiles\*.ps1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, this will include the following files, if they are available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~\.config\dotfiles\dc1\profile.ps1
~\.config\dotfiles\work\profile.ps1
~\.config\dotfiles\personal\extra.ps1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Vim
&lt;/h3&gt;

&lt;p&gt;With Vim or Neovim, you can load multiple files from a directory of your choice, using the &lt;code&gt;runtime&lt;/code&gt; command. For instance, add something like the following to &lt;code&gt;~/.vimrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="k"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt; &lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="sr"&gt;/.config/&lt;/span&gt;custom&lt;span class="sr"&gt;/**/&lt;/span&gt;*&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;vim&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will load any and all files ending with &lt;code&gt;.vim&lt;/code&gt; in &lt;code&gt;~/.config/custom&lt;/code&gt; or any subdirectory thereof. Note that the directory path is relative to the vim runtime path (&lt;code&gt;$VIMRUNTIME&lt;/code&gt;), hence the &lt;code&gt;..&lt;/code&gt; at the beginning of the path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maintaining config files in module directories
&lt;/h2&gt;

&lt;p&gt;The above are examples to demonstrate a theme: create base config files that simply load an arbitrary number of other files, in a directory of your choosing. Once this is done, individual files as well as directories of files can be added to Git repo(s).&lt;/p&gt;

&lt;p&gt;As noted, this requires thinking through directory and file structure carefully, because files are tracked in entirely separate git directories. But that careful organization pays off with simplicity: &lt;code&gt;cd&lt;/code&gt; into the module directory, then use &lt;code&gt;git&lt;/code&gt; as you like, no extra command line options needed. For instance, imagine that we have two modules: &lt;code&gt;base&lt;/code&gt; and &lt;code&gt;personal&lt;/code&gt;, with &lt;code&gt;base&lt;/code&gt; being our main repo in &lt;code&gt;$HOME&lt;/code&gt; and &lt;code&gt;personal&lt;/code&gt; having an additional person Vim config in &lt;code&gt;~/.config/custom/personal/personal.vim&lt;/code&gt;. Initiating the tracking could look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~
git add ~/.vimrc
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"New Vim config"&lt;/span&gt;
git push
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/.config/custom/personal
git add personal.vim
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Addition Vim config for personal laptop"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Other hints and recipes?
&lt;/h2&gt;

&lt;p&gt;I hope this offers you some ideas and inspiration for your own configurations. Please feel free to comment below with suggestions and experiences.&lt;/p&gt;

</description>
      <category>github</category>
      <category>git</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Get Git Default Branch from the Command Line (Powershell or Bash/Zsh)</title>
      <dc:creator>Jonathan Bowman</dc:creator>
      <pubDate>Fri, 29 Jan 2021 12:40:42 +0000</pubDate>
      <link>https://dev.to/bowmanjd/get-github-default-branch-from-the-command-line-powershell-or-bash-zsh-37m9</link>
      <guid>https://dev.to/bowmanjd/get-github-default-branch-from-the-command-line-powershell-or-bash-zsh-37m9</guid>
      <description>&lt;p&gt;On occasion, one needs to know the default branch for a given Git repo. Below I have compiled methods that fit a variety of use cases, and cover specific platforms (Github and Gitlab) as well as methods that work universally regardless of remote platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Github API method
&lt;/h2&gt;

&lt;p&gt;Given a repo &lt;code&gt;username/reponame&lt;/code&gt;, the following script will work in Bash, Dash/Ash, or Zsh, provided you have the &lt;code&gt;wget&lt;/code&gt; tool, and &lt;code&gt;sed&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; - &lt;span class="s2"&gt;"https://api.github.com/repos/username/reponame"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-nE&lt;/span&gt; &lt;span class="s1"&gt;'s/^\s+"default_branch": "([^"]+).+$/\1/p'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have &lt;code&gt;jq&lt;/code&gt; it is even simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; - &lt;span class="s2"&gt;"https://api.github.com/repos/username/reponame"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.default_branch'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or for those who prefer &lt;code&gt;curl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://api.github.com/repos/username/reponame"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.default_branch'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is pretty easy in Powershell, as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;iwr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-useb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://api.github.com/repos/username/reponame&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-exp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;default_branch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In summary, we first query information about the given repo, then parse the JSON that is returned, extracting the value of the &lt;code&gt;default_branch&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;Is this something you might use repeatedly? Consider building a function and placing the following in your shell config (such as &lt;code&gt;.bashrc&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;defbranch &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
  wget &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; - &lt;span class="s2"&gt;"https://api.github.com/repos/&lt;/span&gt;&lt;span class="nv"&gt;$REPO&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-nE&lt;/span&gt; &lt;span class="s1"&gt;'s/^\s+"default_branch": "([^"]+).+$/\1/p'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feel free to &lt;a href="https://gist.github.com/bowmanjd/d72e2e89996087f71ffe3e9777dea5a2/"&gt;use this gist&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or, for Windows Powershell, place the following in your &lt;code&gt;Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;defbranch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kr"&gt;Param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;iwr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-useb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://api.github.com/repos/&lt;/span&gt;&lt;span class="nv"&gt;$repo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-exp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;default_branch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feel free to &lt;a href="https://gist.github.com/bowmanjd/08ccc1af6419d3388aa919aee3497e06"&gt;use this gist&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Similar functionality on Gitlab
&lt;/h2&gt;

&lt;p&gt;The above is specific to Github, because it is specifically a Github setting. In Github, even an empty repo can have a default branch that is different than &lt;code&gt;master&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Gitlab, another popular "social coding" service, has different but similar functionality. A populated repo can have a default branch. An empty one will not.&lt;/p&gt;

&lt;p&gt;Here is an example function that uses the Gitlab API (&lt;a href="https://gist.github.com/bowmanjd/33c34f105302288e667d3105efea297a"&gt;see gist&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gldefbranch &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="s2"&gt;/%2F/g"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;BRANCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;wget &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; - &lt;span class="s2"&gt;"https://gitlab.com/api/v4/projects/&lt;/span&gt;&lt;span class="nv"&gt;$REPO&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-nE&lt;/span&gt; &lt;span class="s1"&gt;'s/.*"default_branch":"([^"]+).*/\1/p'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; null &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;master &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$BRANCH&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the Powershell equivalent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;gldefbranch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kr"&gt;Param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nv"&gt;$repo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"%2F"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nv"&gt;$branch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iwr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-useb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://gitlab.com/api/v4/projects/&lt;/span&gt;&lt;span class="nv"&gt;$repo&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-exp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;default_branch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$branch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$branch&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"master"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A better, universal approach if repo is non-empty
&lt;/h2&gt;

&lt;p&gt;My original intent was to read a Github setting on a repo, new or old, empty or not. However, if desiring to simply get the default branch on an existing repo, an API call is unnecessary. We can use &lt;code&gt;git ls-remote&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://www.reddit.com/r/github/comments/l7rn3j/get_github_default_branch_from_the_command_line/"&gt;u/Cyberbeni in this Reddit discussion&lt;/a&gt; for these suggestions.&lt;/p&gt;

&lt;p&gt;To query the default branch on a remote repository, try this, with your repository URL assigned to or substituted for &lt;code&gt;$REPO_URL&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git ls-remote &lt;span class="nt"&gt;--symref&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REPO_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; HEAD | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-nE&lt;/span&gt; &lt;span class="s1"&gt;'s|^ref: refs/heads/(\S+)\s+HEAD|\1|p'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or Powershell equivalent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ls-remote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--symref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'https://gitlab.com/bowmanjd/dotfiles1.git'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HEAD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;" -replace '.*?^ref: refs/heads/(\S+).+','&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Get local origin/HEAD reference if available
&lt;/h2&gt;

&lt;p&gt;If the repo was downloaded with &lt;code&gt;git clone&lt;/code&gt;, then &lt;code&gt;origin/HEAD&lt;/code&gt; is set automatically. In such cases, the following should work quickly, without needing to query the remote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git symbolic-ref refs/remotes/origin/HEAD | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt; &lt;span class="nt"&gt;-f4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Powershell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;symbolic-ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;refs/remotes/origin/HEAD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-split&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Last&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note the above is unreliable, as it is easily possible to work locally without &lt;code&gt;origin/HEAD&lt;/code&gt; being set.&lt;/p&gt;

&lt;p&gt;Again, thanks to &lt;a href="https://www.reddit.com/r/github/comments/l7rn3j/get_github_default_branch_from_the_command_line/"&gt;u/Cyberbeni for the tip on Reddit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get locally-set Git preference
&lt;/h2&gt;

&lt;p&gt;Of course, if you just want to know the current user's preferred default branch, the following will yield the results, or blank if none has been set up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config init.defaultBranch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One can set this value to, for instance, &lt;code&gt;main&lt;/code&gt; with something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; init.defaultBranch main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy scripting!&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
