<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[geekberg.info]]></title><description><![CDATA[Less terrible tutorials for the open-source crowd. ]]></description><link>https://geekberg.info/</link><image><url>https://geekberg.info/favicon.png</url><title>geekberg.info</title><link>https://geekberg.info/</link></image><generator>Ghost 3.42</generator><lastBuildDate>Fri, 17 Apr 2026 06:19:27 GMT</lastBuildDate><atom:link href="https://geekberg.info/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Not Always Black & White]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>The <a href="https://en.wikipedia.org/wiki/List_of_macOS_built-in_apps#Terminal">terminal emulator</a> that ships with <a href="https://en.wikipedia.org/wiki/MacOS">macOS</a> is, by default, a black (background) and white (text) affair. In addition to a bland aesthetic, this lack of color has practical implications for the user. For example, the output of the <a href="https://www.freecodecamp.org/news/the-linux-ls-command-how-to-list-files-in-a-directory-with-options/">ls</a> command is muted, while files opened in <a href="https://www.vim.org/">vim</a> lack syntax</p>]]></description><link>https://geekberg.info/a-shell-of-a-different-color/</link><guid isPermaLink="false">63c32a60b4e1f7069519486b</guid><dc:creator><![CDATA[casper]]></dc:creator><pubDate>Sun, 15 Jan 2023 00:11:56 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>The <a href="https://en.wikipedia.org/wiki/List_of_macOS_built-in_apps#Terminal">terminal emulator</a> that ships with <a href="https://en.wikipedia.org/wiki/MacOS">macOS</a> is, by default, a black (background) and white (text) affair. In addition to a bland aesthetic, this lack of color has practical implications for the user. For example, the output of the <a href="https://www.freecodecamp.org/news/the-linux-ls-command-how-to-list-files-in-a-directory-with-options/">ls</a> command is muted, while files opened in <a href="https://www.vim.org/">vim</a> lack syntax highlighting.</p>
<p>For those that want to add a little color to their terminal, brightening things up is only a few keystrokes away. First, if it does not already exist, create a <code>.bash_profile</code> in your home directory with: <code>touch ~/.bash_profile</code>, then edit this file with: <code>vim ~/.bash_profile</code> to include the following:</p>
<pre><code>export CLICOLOR=1
export LSCOLORS=GxFxCxDxBxegedabagaced
</code></pre>
<p>Next, open up a terminal and push these changes with: <code>source ~/.bash_profile</code>. Lastly, check &quot;Display ANSI colors&quot; in <code>Terminal -&gt; Preferences -&gt; Profiles -&gt; Text</code> (if needed).</p>
<p>Similarly, adding color to the <code>vim</code> palette involves creating a <code>.vimrc</code> file in your home directory with: <code>touch ~/.vimrc</code>, and then editing the file with vim <code>~/.vimrc</code> to include:</p>
<pre><code>syntax on
</code></pre>
<p>These (colorful) changes take effect once you save your edits.</p>
<p>Cheers.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Seek and Destroy]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p><code>Sed</code>--the <a href="https://en.wikipedia.org/wiki/Sed">stream editor</a>--can read, then  modify text on the fly. While the syntax for <code>sed</code> appears cryptic at first blush, it is a versatile text-processing tool worth adding to your <a href="https://en.wikipedia.org/wiki/Unix">UNIX</a> utility belt.</p>
<p>In the real world, one could use <code>sed</code> in combination with the <a href="https://en.wikipedia.org/wiki/Find_(Unix)">find utility</a> to</p>]]></description><link>https://geekberg.info/seek-and-destroy-with-sed/</link><guid isPermaLink="false">63445021b4e1f706951944e8</guid><dc:creator><![CDATA[casper]]></dc:creator><pubDate>Mon, 24 Oct 2022 17:34:44 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p><code>Sed</code>--the <a href="https://en.wikipedia.org/wiki/Sed">stream editor</a>--can read, then  modify text on the fly. While the syntax for <code>sed</code> appears cryptic at first blush, it is a versatile text-processing tool worth adding to your <a href="https://en.wikipedia.org/wiki/Unix">UNIX</a> utility belt.</p>
<p>In the real world, one could use <code>sed</code> in combination with the <a href="https://en.wikipedia.org/wiki/Find_(Unix)">find utility</a> to recursively locate, then remove: 1) all <a href="https://en.wikipedia.org/wiki/HTML_element">HTML elements</a>, and 2) all leading whitespace from the <a href="https://en.wikipedia.org/wiki/PHP">.php</a> files of a website.</p>
<p>Here's one approach to that task:</p>
<pre><code>#!/usr/bin/env bash

find ./ -type f -name '*.php' \
  -exec sed -i 's/&lt;[^&gt;]*&gt;//g; s/^[ \t]*//g ' {} \;
</code></pre>
<p>Let's take a look at the <code>find</code> portion of the snippet first. For reference, the syntax for this command is:</p>
<p><em>find [location to search] [expression] [-option(s)] [what to find]</em></p>
<p>In our example, we tell <code>find</code> to begin from the present working directory <code>./</code>, and search only for files <code>-type f</code> that end with the <code>'*.php'</code> extension. We then instruct <code>find</code> to <code>-execute</code> two (2) <code>sed</code> commands.</p>
<p>Ouch! Those <code>sed</code> commands look evil. We'll break them down, but before we do, pay attention to the syntax for the command:</p>
<p><em>sed Options... [script] [inputfile]</em></p>
<p>and note the following:</p>
<p><code>-i</code> = in-place edit<br>
<code>s</code> = substitute<br>
<code>g</code> = global<br>
<code>//</code> = delete</p>
<p>Our first expression:</p>
<pre><code>s/&lt;[^&gt;]*&gt;//g
</code></pre>
<ul>
<li>looks for an opening tag <code>&lt;</code></li>
<li>which is followed by zero or more characters <code>*</code>, which are NOT <code>^</code> a closing tag <code>&gt;</code></li>
<li>then looks for a closing tag <code>&gt;</code></li>
</ul>
<p>replacing matches with nothing.</p>
<p>Our second expression:</p>
<pre><code>s/^[ \t]*//g
</code></pre>
<ul>
<li>looks for lines that start <code>^</code> with tabs <code>\t</code></li>
</ul>
<p>replacing matches with nothing.</p>
<p><code>Find</code> then brings it home by recursively <code>{}</code> running our commands, and draws to a close with the semi-colon <code>;</code>.</p>
<p>Viola! With a little elbow grease, we were able to transform reams of text in to a format suitable for export elsewhere.</p>
<p>Cheers.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA["If You Only Knew the Power of the Command Line!"]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>At its core, the command line interface (<a href="https://en.wikipedia.org/wiki/Command-line_interface">CLI</a>) grants users control over complex systems, and allows for the automation of tasks that would otherwise be cumbersome to manage manually. What follows is a brief transcript that helps to illustrate the point.</p>
<pre><code>last | awk '{print $3}' |grep -E '128.</code></pre>]]></description><link>https://geekberg.info/if-they-only-new-the-power-of-the-command-line/</link><guid isPermaLink="false">626dad1ab4e1f706951944b9</guid><dc:creator><![CDATA[casper]]></dc:creator><pubDate>Tue, 03 May 2022 18:10:04 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>At its core, the command line interface (<a href="https://en.wikipedia.org/wiki/Command-line_interface">CLI</a>) grants users control over complex systems, and allows for the automation of tasks that would otherwise be cumbersome to manage manually. What follows is a brief transcript that helps to illustrate the point.</p>
<pre><code>last | awk '{print $3}' |grep -E '128.122..'|sort -V |uniq &gt;&gt; publicIPs
</code></pre>
<p>Here, from our host node, we get a list of logins via the <code>last</code> command, extracting only the third (3rd) column of the output via <code>awk</code>, then <code>grep</code> for IPs on the <code>128.122</code> subnet. From there, we sort said IPs numerically, and output uniq IPs to a list (<em>&quot;publicIPs&quot;</em>). For reference:</p>
<pre><code>128.122.87.178
128.122.113.43
128.122.113.212
128.122.114.69
128.122.114.139
128.122.115.60
</code></pre>
<p>Next, let's iterate through this list (by line), performing an <code>nslookup</code> on each IP, and parse our region of interest (ROI),then add this ROI to a file (<em>&quot;dnsRecs&quot;</em>).</p>
<pre><code>while IFS= read -r line; do
  nslookup &quot;$line&quot; |awk 'FNR == 1 {print $4}' &gt;&gt; dnsRecs
done &lt; publicIPs
</code></pre>
<p>By way of example (FQDN omitted):</p>
<pre><code>plabrigla
publio
tamino
figaro
keystone
syndrome
</code></pre>
<p>Then, let's iterate through the list of DNS records, using <a href="https://nmap.org/">nmap</a> to gather some intelligence on the hosts:</p>
<pre><code>while IFS= read -r line; do
  echo &quot;$line&quot; &gt;&gt; osRecs
  nmap -O &quot;$line&quot; &gt;&gt; osRecs
done &lt; dnsRecs
</code></pre>
<p>For reference (single host, DNS ommitted):</p>
<pre><code>Not shown: 996 closed ports
PORT      STATE    SERVICE
22/tcp    filtered ssh
3283/tcp  open     netassistant
5900/tcp  open     vnc
28201/tcp open     unknown
Aggressive OS guesses: VMware ESXi 3.5 (91%), FreeBSD 6.1-RELEASE (88%),   FreeBSD 6.2-RELEASE (88%), Apple iOS 4.3.1 - 4.3.5 (87%), 
Apple iOS 6 (87%), Apple iOS 8.2 (Darwin 14.0.0) (87%), Apple OS X 10.10 (Yosemite) or iOS 8.3 - 9.0.1 (Darwin 14.0.0 - 15.0.0) (87
%), Apple iPhone OS 3.1.2 - iOS 4.2.1 (87%), Apple Mac OS X 10.5 (Leopard) (Darwin 9.2.2, x86) (87%), Apple Mac OS X 10.5.5 (Leopar
d) - 10.6.6 (Snow Leopard) (Darwin 9.5.0 - 10.6.0) (87%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 8 hops
</code></pre>
<p>Cheers.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Colon Cleaner]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Here's a quick-and-dirty <code>bash</code> snippet that recursively locates all files ending with the <code>.npy</code> extension, and then replaces occurrences of a colon(<code>:</code>) with a dash (<code>-</code>) in their filename . For reference:</p>
<pre><code>find ./ -depth -type f -name '*.npy' \
    -exec bash -c 'mv &quot;$0&quot; &quot;${0//:/-}&quot;'</code></pre>]]></description><link>https://geekberg.info/colon-cleaner/</link><guid isPermaLink="false">61ca0409eaafea06847032a3</guid><dc:creator><![CDATA[casper]]></dc:creator><pubDate>Sun, 16 Jan 2022 16:58:19 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Here's a quick-and-dirty <code>bash</code> snippet that recursively locates all files ending with the <code>.npy</code> extension, and then replaces occurrences of a colon(<code>:</code>) with a dash (<code>-</code>) in their filename . For reference:</p>
<pre><code>find ./ -depth -type f -name '*.npy' \
    -exec bash -c 'mv &quot;$0&quot; &quot;${0//:/-}&quot;' {} \;
</code></pre>
<p>If you'd like, you could run the following to create a sandbox for testing:</p>
<pre><code>#!/usr/bin/env bash
# Create time stamp

timestamp=$(date +&quot;%H:%M:%S&quot;)

# Create three (3) dummy directories with two-hundred (200) files in each. A file will take this form: 
dummy-data-000:20:12:00.npy

mkdir -pv data{0..2} &amp;&amp; touch data{0..2}/dummy-data-{0..1}{0..9}{0..9}:&quot;$timestamp&quot;.npy
</code></pre>
<p>Cheers.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Faster Than a Speeding Bullet?]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p><a href="https://www.speedtest.net/">Speed Test by Ookla</a> is a web service that<br>
measures Internet connection performance metrics like download/upload rate, latency, and packet loss. The company offers a browser-based test, as well as several dedicated applications for the same, though you can bypass all of that with this snippet:</p>
<pre><code>wget --output-document=/dev/</code></pre>]]></description><link>https://geekberg.info/faster-than-a-speeding-bullet/</link><guid isPermaLink="false">61993520eaafea068470328b</guid><dc:creator><![CDATA[casper]]></dc:creator><pubDate>Sun, 21 Nov 2021 16:55:50 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p><a href="https://www.speedtest.net/">Speed Test by Ookla</a> is a web service that<br>
measures Internet connection performance metrics like download/upload rate, latency, and packet loss. The company offers a browser-based test, as well as several dedicated applications for the same, though you can bypass all of that with this snippet:</p>
<pre><code>wget --output-document=/dev/null http://speedtest.wdc01.softlayer.com/downloads/test100.zip
</code></pre>
<p>Here we ask <a href="https://www.gnu.org/software/wget/">wget</a> to download a 100 MB zip file from Speed Test, and send the result to <code>/dev/null</code>, where it's immediately discarded. The results are conveyed in real-time to your terminal, e.g.:</p>
<pre><code>--2021-11-21 10:40:00--  http://speedtest.wdc01.softlayer.com/downloads/test100.zip
Resolving speedtest.wdc01.softlayer.com (speedtest.wdc01.softlayer.com)... 169.54.48.218, 2607:f0d0:3006:154::3
Connecting to speedtest.wdc01.softlayer.com (speedtest.wdc01.softlayer.com)|169.54.48.218|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 104874307 (100M) [application/zip]
Saving to: ‘/dev/null’

100%[====================================================================================================&gt;] 104,874,307 72.5MB/s   in 1.4s 

2021-11-21 10:40:01 (72.5 MB/s) - ‘/dev/null’ saved [104874307/104874307]
</code></pre>
<p>Keep in mind that this command-line test only measures download speed, and the result is limited by the lower of the maximum speed the source/destination can offer. Also, it's useful to poll multiple sources, and run multiple tests. To perform the latter, you can do:</p>
<pre><code>for run in {1..5}; wget --output-document=/dev/null http://speedtest.wdc01.softlayer.com/downloads/test100.zip; done
</code></pre>
<p>which runs the test five (5) times sequentially.</p>
<p>For more information on Speed Test results, see <a href="https://www.speedtest.net/about/knowledge/faq">here</a>. Also, check out the command line application <a href="https://manpages.ubuntu.com/manpages/xenial/man1/speedtest-cli.1.html">here</a>.</p>
<p>Cheers.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Automating the Boring Stuff, Too]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In our <a href="https://geekberg.info/automating-the-boring-stuff/">last post</a> we discussed using <a href="https://www.gnu.org/software/bash/">Bash</a> to transform data in one format to an arrangement suitable for import in to  <a href="https://groups.google.com">Google Groups</a>. Now, let's take a soup-to-nuts look at the process.</p>
<p>For reference, our source data takes the following format:</p>
<p><code># list-o-Steves</code><br>
<code>#</code><br>
<code>Stephen Gary Wozniak &lt;imalegend@example.com&</code></p>]]></description><link>https://geekberg.info/automate-the-boring-stuff-ii/</link><guid isPermaLink="false">6151f48beaafea068470326e</guid><dc:creator><![CDATA[casper]]></dc:creator><pubDate>Sun, 03 Oct 2021 15:57:15 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>In our <a href="https://geekberg.info/automating-the-boring-stuff/">last post</a> we discussed using <a href="https://www.gnu.org/software/bash/">Bash</a> to transform data in one format to an arrangement suitable for import in to  <a href="https://groups.google.com">Google Groups</a>. Now, let's take a soup-to-nuts look at the process.</p>
<p>For reference, our source data takes the following format:</p>
<p><code># list-o-Steves</code><br>
<code>#</code><br>
<code>Stephen Gary Wozniak &lt;imalegend@example.com&gt;</code><br>
<code>Stephen William Hawking &lt;imaboygenius@example.com&gt;</code><br>
<code>Steven Anthony Ballmer &lt;imapc@example.com&gt;</code><br>
<code>Steve Austin &lt;imastonecoldsunofagun@example.com&gt;</code><br>
<code>Steven Paul Jobs &lt;imamac@example.com&gt;</code><br>
<code>Stevland Hardaway Morris &lt;imawonder@example.com&gt;</code>.</p>
<p>and each individual record in the source needs to be modified to the following:</p>
<p><code>,&lt;email@example.com&gt;,USER,MEMBER,Firstname</code>.</p>
<p>So how can we get there? Well, we can start by discarding lines that begin with a <code>#</code> in our source file, as they are irrelevant. To do that, tell <a href="https://www.gnu.org/software/grep/manual/grep.html">grep</a> to <strong>not</strong> (<code>^</code>) print lines that begin with <code>#</code>:</p>
<p><code>grep &quot;^[^#]&quot; &quot;$file&quot;</code>.</p>
<p>This yields:</p>
<p><code>Stephen Gary Wozniak &lt;imalegend@example.com&gt;</code><br>
<code>Stephen William Hawking &lt;imaboygenius@example.com&gt;</code><br>
<code>Steven Anthony Ballmer &lt;imapc@example.com&gt;</code><br>
<code>Steve Austin &lt;imastonecoldsunofagun@example.com&gt;</code><br>
<code>Steven Paul Jobs &lt;imamac@example.com&gt;</code><br>
<code>Stevland Hardaway Morris &lt;imawonder@example.com&gt;</code>.</p>
<p>Notice that in each row of the returned list, a column is separated by one (1) blank space. That's awfully useful for our purposes, as we can inform  <a href="https://www.gnu.org/software/gawk/manual/gawk.html">awk</a> that a blank space <code>&quot; &quot;</code> indicates a separate field (<code>-F &quot; &quot;</code>). Then, when <code>awk</code> reads through each row, we can tell it exactly what we want to print:</p>
<p><code>'{print &quot;&quot;, $NF, &quot;USER&quot;, &quot;MEMBER&quot;, $1}'</code>,</p>
<p>which yields:</p>
<p><code>,&lt;imalegend@example.com&gt;,USER,MEMBER,Stephen</code><br>
<code>,&lt;imaboygenius@example.com&gt;,USER,MEMBER,Stephen</code><br>
<code>,&lt;imapc@example.com&gt;,USER,MEMBER,Steven</code><br>
<code>,&lt;imastonecoldsunofagun@example.com&gt;,USER,MEMBER,Steve</code><br>
<code>,&lt;imamac@example.com&gt;,USER,MEMBER,Steven</code><br>
<code>,&lt;imawonder@example.com&gt;,USER,MEMBER,Stevland</code></p>
<p>That's nifty! Here's a breakdown:</p>
<table>
<thead>
<tr>
<th>&quot;&quot;</th>
<th>$NF</th>
<th>&quot;USER&quot;</th>
<th>&quot;MEMBER&quot;</th>
<th>$1</th>
</tr>
</thead>
<tbody>
<tr>
<td>(blank)</td>
<td>last column of row</td>
<td>USER</td>
<td>MEMBER</td>
<td>first column of row</td>
</tr>
</tbody>
</table>
<p>Taken all together:</p>
<p><code>grep &quot;^[^#]&quot; $file | awk -v OFS=, -F &quot; &quot; '{print &quot;&quot;, $NF, &quot;USER&quot;, &quot;MEMBER&quot;, $1}'</code>,</p>
<p>wherein we <a href="https://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO-4.html">pipe</a> (<code>|</code>) the results from <code>grep</code> to <code>awk</code>, and tell <code>awk</code> to separate each value (<code>-v OFC=,</code>) it prints with a comma (,). We can then extend the example by <a href="https://www.gnu.org/software/bash/manual/html_node/Redirections.html">redirecting</a> standard output to the <code>.csv</code> file we created in <a href="https://geekberg.info/automating-the-boring-stuff/">Part 1</a> of this project with:</p>
<p><code>&gt;&gt; &quot;$file&quot;.&quot;$file_type&quot;</code>.</p>
<p>The result? A nicely formatted <code>.csv</code> file that's easily imported in to Google Groups:</p>
<p><code>Group Email [Required], Member Email, Member Type, Member Role, Member Name</code><br>
<code>,&lt;imalegend@example.com&gt;,USER,MEMBER,Stephen</code><br>
<code>,&lt;imaboygenius@example.com&gt;,USER,MEMBER,Stephen</code><br>
<code>,&lt;imapc@example.com&gt;,USER,MEMBER,Steven</code><br>
<code>,&lt;imastonecoldsunofagun@example.com&gt;,USER,MEMBER,Steve</code><br>
<code>,&lt;imamac@example.com&gt;,USER,MEMBER,Steven</code><br>
<code>,&lt;imawonder@example.com&gt;,USER,MEMBER,Stevland</code>.</p>
<p>Next, one could iterate over all of their lists using the snippets provided, and &quot;Automate the Boring Stuff&quot; without a line of Python. We did exactly that <a href="https://gist.github.com/marshki/9568d9acd96620d5b6db4bf1bac91a2a">here</a>.</p>
<p>Cheers.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Automating the Boring Stuff]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In his book: &quot;<a href="https://automatetheboringstuff.com/">Automating the Boring Stuff</a>&quot;, Al Sweigart details how to automate boring, repetitive digital tasks using the <a href="https://www.python.org/">Python</a> programming language. A job that could take hours of manual work, he explains, can be completed by a computer program faster--and with greater accuracy--than a human being.</p>
<p>Python</p>]]></description><link>https://geekberg.info/automating-the-boring-stuff/</link><guid isPermaLink="false">614f3518e063f806b466d8ea</guid><dc:creator><![CDATA[casper]]></dc:creator><pubDate>Sun, 26 Sep 2021 16:11:52 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>In his book: &quot;<a href="https://automatetheboringstuff.com/">Automating the Boring Stuff</a>&quot;, Al Sweigart details how to automate boring, repetitive digital tasks using the <a href="https://www.python.org/">Python</a> programming language. A job that could take hours of manual work, he explains, can be completed by a computer program faster--and with greater accuracy--than a human being.</p>
<p>Python can add a layer of complexity to a pipeline's automation, though. For example, Python frequently requires the import of one or more modules to achieve a task that a command-line interpreter such as <a href="https://www.gnu.org/software/bash/">Bash</a> can do directly.</p>
<p>Let's look at a real world example: migrating scores of mailing lists from a decrepit <a href="https://en.wikipedia.org/wiki/Sendmail">Sendmail</a> server to <a href="https://groups.google.com">Google Groups</a>. Our source material takes the following form (abridged):</p>
<p><code># List-o-Steves</code><br>
<code>#</code><br>
<code>Stephen Gary Wozniak &lt;imalegend@example.com&gt;</code><br>
<code>Stephen William Hawking &lt;imaboygenius@example.com&gt;</code><br>
<code>Steven Anthony Ballmer &lt;imapc@example.com&gt;</code><br>
<code>Steve Austin &lt;imastonecoldsunofagun@example.com&gt;</code><br>
<code>Steven Paul Jobs &lt;imamac@example.com&gt;</code><br>
<code>Stevland Hardaway Morris &lt;imawonder@example.com&gt;</code>,</p>
<p>while Google Groups requires formatted <code>.csv</code> files that look like this:</p>
<p><code>Header: Group Email [required],Member Email,Member Type,Member Role</code><br>
<code>Entry: yourgroup@email.com, membername@email.com,USER,MEMBER</code>.</p>
<p>For reference:<br>
<img src="https://geekberg.info/content/images/2021/09/ggroups.png" alt="ggroups">.</p>
<p>The task, then, is to extract data from our source, and transform that data to a format suitable for import in to Google Groups. But how?</p>
<p>Well, let's start by declaring our variables:</p>
<p><code>domain=&quot;example.com&quot;</code><br>
<code>file_type=&quot;csv&quot;</code><br>
<code>file=&quot;list-o-steves&quot;</code></p>
<p>and creating a <code>.csv</code> file with appropriately-named headers:</p>
<p><code>printf &quot;%s\n&quot; &quot;Group Email [Required], Member Email, Member Type, Member Role, Member Name&quot; &gt; &quot;$file&quot;.&quot;$file_type&quot;</code></p>
<p>which returns this file:</p>
<p><code>list-o-steves.csv</code></p>
<p>with the following on its first line:</p>
<p><code>Group Email [Required], Member Email, Member Type, Member Role, Member Name</code></p>
<p>Progress! We'll cover the nuts-and-bolts of extracting our data in a coming post.</p>
<p>Cheers.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[No Hangups]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In a <a href="https://geekberg.info/tar-from-afar/">previous post</a> we detailed how to archive data &quot;on-the-fly&quot; from a local source to a remote destination, using the utilities <code>tar</code>, <code>ssh</code>, and <code>cat</code>. Today, we'll add <a href="https://ss64.com/bash/nohup.html">nohup</a> to that pipeline so that it can continue uninterrupted, irrespective of your current connection's status (a broken SSH</p>]]></description><link>https://geekberg.info/no-hangups/</link><guid isPermaLink="false">60ae7b876aa10606a6435050</guid><dc:creator><![CDATA[casper]]></dc:creator><pubDate>Sun, 19 Sep 2021 20:33:41 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>In a <a href="https://geekberg.info/tar-from-afar/">previous post</a> we detailed how to archive data &quot;on-the-fly&quot; from a local source to a remote destination, using the utilities <code>tar</code>, <code>ssh</code>, and <code>cat</code>. Today, we'll add <a href="https://ss64.com/bash/nohup.html">nohup</a> to that pipeline so that it can continue uninterrupted, irrespective of your current connection's status (a broken SSH session, e.g.).</p>
<p>Here goes:</p>
<p>First, preface your pipeline with the <code>nohup</code> command:</p>
<p><code>nohup tar --gzip --create --verbose --file - foobar | ssh foo@foobarserver &quot;cat &gt; /path/to/foobar/foobar.tgz&quot;</code></p>
<p>Second, stop the command using the following keyboard combination:</p>
<p><kbd>CTRL</kbd>+<kbd>Z</kbd></p>
<p>Third, place this foreground process into the background with:</p>
<p><code>bg</code>.</p>
<p>You'll then receive output similar to the following in your shell:</p>
<p><code>PID 26935: completed</code><br>
<code>PID 8072: in progress (foobar.tgz)</code>.</p>
<p>Pay attention to the latter process ID (<code>PID</code>), as that will be the number you'll reference when checking on the job's status using, e.g.: <code>top -p 8072</code>. For an overview of <code>jobs</code>, and background/foreground processes see <a href="https://www.redhat.com/sysadmin/jobs-bg-fg">this article</a> from <a href="https://www.redhat.com/en">Red Hat</a>.</p>
<p>Cheers.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Mail It In]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h1 id="setupsendmailingnulinux">Setup Sendmail in GNU/Linux</h1>
<p>Scope: Set up a general-purpose <a href="https://en.wikipedia.org/wiki/Internetworking">internetwork</a> routing facility that supports <a href="https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol">Simple Mail Transfer Protocol</a> (SMTP).</p>
<h3 id="dependencies">Dependencies</h3>
<p>Install these packages first, per your operating system (OS):</p>
<table>
<thead>
<tr>
<th>Fedora, CentOS</th>
<th>Debian, Ubuntu</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>dnf install</code></td>
<td><code>apt-get install</code></td>
</tr>
<tr>
<td>sendmail-cf</td>
<td>sendmail-cf</td>
</tr>
<tr>
<td>mailutils</td>
<td>mailutils</td>
</tr>
<tr>
<td>sendmail</td>
<td>sendmail</td>
</tr>
</tbody>
</table>
<h3 id="disablesendmaildaemon">Disable Sendmail daemon</h3>
<p><a href="http://www.deer-run.com/~hal/sysadmin/sendmail.html">Improve Sendmail security</a></p>]]></description><link>https://geekberg.info/setting-up-sendmail/</link><guid isPermaLink="false">5f9ee9358969f906a8b224b3</guid><dc:creator><![CDATA[casper]]></dc:creator><pubDate>Sun, 01 Nov 2020 18:07:49 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h1 id="setupsendmailingnulinux">Setup Sendmail in GNU/Linux</h1>
<p>Scope: Set up a general-purpose <a href="https://en.wikipedia.org/wiki/Internetworking">internetwork</a> routing facility that supports <a href="https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol">Simple Mail Transfer Protocol</a> (SMTP).</p>
<h3 id="dependencies">Dependencies</h3>
<p>Install these packages first, per your operating system (OS):</p>
<table>
<thead>
<tr>
<th>Fedora, CentOS</th>
<th>Debian, Ubuntu</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>dnf install</code></td>
<td><code>apt-get install</code></td>
</tr>
<tr>
<td>sendmail-cf</td>
<td>sendmail-cf</td>
</tr>
<tr>
<td>mailutils</td>
<td>mailutils</td>
</tr>
<tr>
<td>sendmail</td>
<td>sendmail</td>
</tr>
</tbody>
</table>
<h3 id="disablesendmaildaemon">Disable Sendmail daemon</h3>
<p><a href="http://www.deer-run.com/~hal/sysadmin/sendmail.html">Improve Sendmail security</a> by turning it <em>off</em>.</p>
<p><code>systemctl stop sendmail</code></p>
<p><code>systemctl disable sendmail</code></p>
<h3 id="preflightchecks">Preflight checks</h3>
<p>You'll need these.</p>
<ul>
<li>Check for required files:</li>
</ul>
<p><code>ls /etc/mail/submit.*</code></p>
<p>should yield:</p>
<p><code>/etc/mail/submit.cf</code></p>
<p><code>/etc/mail/submit.mc</code></p>
<ul>
<li>Check for <code>m4</code> processor:</li>
</ul>
<p><code>which m4</code></p>
<p>should yield:</p>
<p><code>/usr/bin/m4</code></p>
<h3 id="buildsubmitcffile">Build <code>submit.cf</code> file</h3>
<p>The secret sauce.</p>
<ul>
<li>Backup <code>submit.cf</code>:</li>
</ul>
<p><code>cp -pv /etc/mail/submit.cf /etc/mail/submit.cf.bak</code></p>
<ul>
<li>Create <code>mysubmit.mc</code> macro configuration file by copying default macro configuration file:</li>
</ul>
<p><code>cp -pv submit.mc mysubmit.mc</code></p>
<ul>
<li>Modify <code>mysubmit.mc</code>,</li>
</ul>
<p>replacing:</p>
<p><code>[127.0.0.1]</code></p>
<p>with:</p>
<p><code>mailhost</code></p>
<p>on the <code>msp</code> line so that is looks like this:</p>
<pre><code>FEATURE(`msp', `mailhost')dnl`
</code></pre>
<ul>
<li>Commit your change (from <code>/etc/mail</code>):</li>
</ul>
<p><code>m4 mysubmit.mc &gt; /etc/mail/submit.cf</code></p>
<h3 id="modifyetchostsfile">Modify <code>/etc/hosts</code> file</h3>
<p>More goodness.</p>
<ul>
<li>List the host's IP &amp; hostname, e.g.:</li>
</ul>
<p><code>IPaddress hostname alias</code></p>
<ul>
<li>List an SMTP client:</li>
</ul>
<p><code>IPaddress hostname alias</code></p>
<p>Cheers.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[A Safety Net for Your Shell]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>For the risk-averse among us, here are a few <a href="https://tldp.org/LDP/abs/html/aliases.html">aliases</a> that can reside in your shell initialization as insurance against the accidental removal of a file(s) or folder(s):</p>
<p><code>alias cp=&quot;cp -i&quot;</code><br>
<code>alias mv=&quot;mv -i&quot;</code><br>
<code>alias rm=&quot;rm -i&quot;</code>.</p>
<p>Simply add</p>]]></description><link>https://geekberg.info/shell-safety-net/</link><guid isPermaLink="false">5f9593ea8969f906a8b224ac</guid><dc:creator><![CDATA[casper]]></dc:creator><pubDate>Sun, 25 Oct 2020 16:46:16 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>For the risk-averse among us, here are a few <a href="https://tldp.org/LDP/abs/html/aliases.html">aliases</a> that can reside in your shell initialization as insurance against the accidental removal of a file(s) or folder(s):</p>
<p><code>alias cp=&quot;cp -i&quot;</code><br>
<code>alias mv=&quot;mv -i&quot;</code><br>
<code>alias rm=&quot;rm -i&quot;</code>.</p>
<p>Simply add them to your <code>~/.bash_profile</code> with the following:</p>
<p><code>printf '%s\n' 'alias cp=&quot;cp -i&quot;' 'alias mv=&quot;mv -i&quot;' 'alias rm=&quot;rm -i&quot;' &gt;&gt;~/.bash_profile</code>,</p>
<p>being sure to reload your <a href="https://friendly-101.readthedocs.io/en/latest/bashprofile.html">Bash profile</a> by doing: <code>. ~/.bash_profile</code></p>
<p>Now, when calling the <code>rm</code> command, for example, rather that carelessly expunging your data, you'll receive an interactive prompt asking you if you want to remove a given file. You can then accept or decline the action as shown here:</p>
<table>
<thead>
<tr>
<th>Without Alias</th>
<th>With Alias</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>rm derpy.txt</code></td>
<td><code>rm derpy.txt</code></td>
</tr>
<tr>
<td></td>
<td><code>rm: remove regular file'derpy.txt'?</code></td>
</tr>
</tbody>
</table>
<p>Aliases need not be limited to &quot;defensive&quot; parameters, though. In fact, they are a useful tool that can save you keystrokes, and by extenstion, time.</p>
<p>Cheers.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Tar From Afar]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>One benefit of working from a <a href="https://en.wikipedia.org/wiki/Terminal_emulator">terminal emulator</a> is the ability to chain commands together in a way that would be difficult to do efficiently from a <a href="https://en.wikipedia.org/wiki/Graphical_user_interface">GUI</a>. For example, the following snippet allows for &quot;on the fly&quot; compression and transfer of a local directory to a remote</p>]]></description><link>https://geekberg.info/tar-from-afar/</link><guid isPermaLink="false">5ec936ca6a27b606a74a3b5a</guid><dc:creator><![CDATA[casper]]></dc:creator><pubDate>Sat, 23 May 2020 16:39:04 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>One benefit of working from a <a href="https://en.wikipedia.org/wiki/Terminal_emulator">terminal emulator</a> is the ability to chain commands together in a way that would be difficult to do efficiently from a <a href="https://en.wikipedia.org/wiki/Graphical_user_interface">GUI</a>. For example, the following snippet allows for &quot;on the fly&quot; compression and transfer of a local directory to a remote location:</p>
<p><code>tar --gzip --create --verbose --file - foobar | ssh foo@foobarserver &quot;cat &gt; /path/to/foobar/foobar.tgz&quot;</code></p>
<p>Here <code>tar</code> calls the <code>gzip</code> <a href="https://en.wikipedia.org/wiki/Gzip">utility</a> to create an archive of the the &quot;foobar&quot; directory, and then pipes (<code>|</code>) the compressed data over a <a href="https://en.wikipedia.org/wiki/Secure_Shell">secure shell</a> (<code>ssh</code>) connection. As the data reaches the remote location, it is fed to <code>cat</code> and redirected <code>&gt;</code> to the tarball: &quot;foobar.tgz&quot;.</p>
<p>What's nice about this item is that you don't need to tar your data in its entirety before you transfer it; rather, as your data is compressed, it is transferred, then appended, to the remote archive in one fell swoop. This is particularly useful if your local node is running low on disk space and you want to free up space in a hurry. Try doing <em>that</em> with your GUI.</p>
<p>Cheers.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[UIDs + 1]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Here's a quick code snippet for use in extracting the current highest<br>
<a href="https://en.wikipedia.org/wiki/User_identifier">user identifier</a> (<em>UID</em>) in GNU/Linux using <a href="https://en.wikipedia.org/wiki/AWK">awk</a> to do so. For reference: a UID is the unique value your UNIX-like operating system (<em>OS</em>) uses to identify an account and, when used in combination with other components of</p>]]></description><link>https://geekberg.info/1-user-ids/</link><guid isPermaLink="false">5e64fab280056906aefacf17</guid><dc:creator><![CDATA[casper]]></dc:creator><pubDate>Sun, 08 Mar 2020 15:38:03 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Here's a quick code snippet for use in extracting the current highest<br>
<a href="https://en.wikipedia.org/wiki/User_identifier">user identifier</a> (<em>UID</em>) in GNU/Linux using <a href="https://en.wikipedia.org/wiki/AWK">awk</a> to do so. For reference: a UID is the unique value your UNIX-like operating system (<em>OS</em>) uses to identify an account and, when used in combination with other components of the OS, determines what system resources said account may access.</p>
<p>UIDs are stored in the world-readable <code>/etc/passwd</code> file, described thoroughly by The Linux Information Project (<em>LINFO</em>) <a href="http://www.linfo.org/etc_passwd.html">here</a>. In a given entry (e.g.:<br>
<code>linus:x:1001:1001::/home/linus:/bin/bash</code>), each of the seven (7) fields (name, password, user ID, group ID, gecos, home directory, shell) are separated by a colon (<em>:</em>) with no spaces.</p>
<p>To pull a UID from the <code>/etc/passwd</code> file via <code>awk</code>, then, one would use the <code>-F</code> flag and set a colon (<code>:</code>) as the field separator, printing the third column (<code>{print $3}</code>), provided the condition exists. As we're looking for the highest UID, we pipe our results <code>| tail -1</code> to pull the very last entry from <code>/etc/passwd</code>. With that number in hand, we can simply add to it with a <code>+1</code>.</p>
<p>Take a look:</p>
<pre><code>get_uid() {
  uid=$(awk -F &quot;:&quot; '/1/ {print $3}' /etc/passwd |tail -1)
  increment_uid=$((uid +1))

  printf &quot;%s\\n&quot; &quot;Assigning uid:$increment_uid to $username&quot;
}
</code></pre>
<p>Cheers.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Creating Default Directory Structure in GNU/Linux]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In a previous post, we <a href="https://github.com/marshki/plus_1">shared</a> a Bash <a href="http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_02_01.html">shell script</a> for creating local user accounts in <a href="https://www.gnu.org/gnu/linux-and-gnu.en.html">GNU/Linux</a>. As is often the case, the process of drafting this script yielded a number of useful code snippets, one of which we'll detail in this post.</p>
<p>By default, the <code>useradd</code> <a href="https://linux.die.net/man/8/useradd">command</a> will</p>]]></description><link>https://geekberg.info/creating-default-directory-structure-in-ubuntu/</link><guid isPermaLink="false">5e25b1675e814d069e58bc5a</guid><dc:creator><![CDATA[casper]]></dc:creator><pubDate>Mon, 20 Jan 2020 16:08:46 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>In a previous post, we <a href="https://github.com/marshki/plus_1">shared</a> a Bash <a href="http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_02_01.html">shell script</a> for creating local user accounts in <a href="https://www.gnu.org/gnu/linux-and-gnu.en.html">GNU/Linux</a>. As is often the case, the process of drafting this script yielded a number of useful code snippets, one of which we'll detail in this post.</p>
<p>By default, the <code>useradd</code> <a href="https://linux.die.net/man/8/useradd">command</a> will not create the directory structure that GUI users of GNU/Linux distributions may be familiar with (e.g.: <code>Desktop</code>, <code>Music</code>, and the like). While this isn't necessarily a deal-breaker, there may be use cases where this structure is desirable.</p>
<p>To create the default directory structure, we need to call on the <code>xdg-user-dirs</code> tool (for reference: <a href="https://wiki.archlinux.org/index.php/XDG_user_directories">https://wiki.archlinux.org/index.php/XDG_user_directories</a>). Here's one way to do so:</p>
<pre><code>#!/usr/bin/env bash

username='sjobs'

create_default_dirs () { 
  if [[ -n $(command -v xdg-user-dirs-update) ]]
  then
  su ${username} -c xdg-user-dirs-update  
  $username -i xdg-user-dirs-update
fi
} 
</code></pre>
<p>In our snippet, we ask <code>Bash</code> to print the path of the <code>xdg-users-dirs-update</code> command (<code>command -v</code>), checking that the length of this check is <em>not</em> zero (0). Once confirmed, we then run the <code>xdg-...</code> command as the user (<code>sjobs</code>, in this case) of the account our script created (<code>su ${username} -c</code>), such that the default directory structure is placed in this user's <code>home</code> folder.</p>
<p>Cheers.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Passing Secrets When Adding Users in GNU/Linux]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>For those running multi-user GNU/Linux instances <strong>not</strong> bound to a network <a href="https://en.wikipedia.org/wiki/Identity_management_system">identity management</a> tool, there are a number of ways to provision local user accounts. As we're a fan of automating repetitive tasks such as the aforementioned, we'll examine how to handle silent password creation when adding users via</p>]]></description><link>https://geekberg.info/silent-password-passing-in-gnu-linux/</link><guid isPermaLink="false">5d3c72e75c419e06995a4f88</guid><dc:creator><![CDATA[casper]]></dc:creator><pubDate>Fri, 04 Oct 2019 13:48:37 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>For those running multi-user GNU/Linux instances <strong>not</strong> bound to a network <a href="https://en.wikipedia.org/wiki/Identity_management_system">identity management</a> tool, there are a number of ways to provision local user accounts. As we're a fan of automating repetitive tasks such as the aforementioned, we'll examine how to handle silent password creation when adding users via a shell script.</p>
<p>When creating a user account, we need, at a minimum, a username and password. Here's a function to probe for a password:</p>
<pre><code>get_password () { 

  while true
  
  do      
    read -r -s -p &quot;Enter password to add and press [Enter]: &quot; pass1 
    printf &quot;\\n&quot; 
    read -r -s -p &quot;Re-enter password to add and press [Enter]: &quot; pass2 
    printf &quot;\\n&quot; 

    if [[ &quot;$pass1&quot; != &quot;$pass2&quot; ]]; then 
      printf &quot;%s\n&quot; &quot;ERROR: Passwords do no match.&quot;
    else 
      printf &quot;%s\n&quot; &quot;Passwords match. Continuing...&quot;
      break
    fi 
  done
} 
</code></pre>
<p>In this snippet, we use a <a href="http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_02.html">while loop</a> to prompt for a password twice,  then check that both user-provided inputs match. Note the <code>read</code> flags: <code>-r</code> (to prevent the <code>\</code> character from acting as an escape character; <code>-s</code> (to pass input silently, i.e. not print the password to <a href="https://stackoverflow.com/questions/3385201/confused-about-stdin-stdout-and-stderr">standard output</a>), and <code>-p</code> (to display the prompt).</p>
<p>Having quietly retrieved the password, we next need to create the user account, and then silently set a password for the same. Here's a function for doing so:</p>
<pre><code>set_password_linux() { 

  printf &quot;%s\\n&quot; &quot;Setting password...&quot; 

  printf &quot;%s&quot; &quot;$username:$pass2&quot; | chpasswd 
}
</code></pre>
<p>We use the <code>printf</code> command with the stored variables for the username and password, then pipe (<code>|</code>) that information to the <code>chpasswd</code> command. In this way, we've securely obtained and pushed out a password that never appears on screen.</p>
<p>For the full-monty, <a href="https://github.com/marshki/plus_1/blob/master/src/plus_1.sh">see here</a>.</p>
<p>Cheers.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[(W)get at the Truth]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In a previous post, we discussed HTTP response codes, and shared a snippet for using <a href="https://curl.haxx.se/">curl</a> to extract a website's <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">response code</a> for the purpose of checking if that site was accessible. Today, we'll show how to use <a href="https://www.gnu.org/software/wget/">wget</a> to achieve the same effect.</p>
<p>Here's the gist of how to</p>]]></description><link>https://geekberg.info/wget-at-the-truth/</link><guid isPermaLink="false">5cddedb2678e4006827a2549</guid><dc:creator><![CDATA[casper]]></dc:creator><pubDate>Fri, 17 May 2019 01:01:22 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>In a previous post, we discussed HTTP response codes, and shared a snippet for using <a href="https://curl.haxx.se/">curl</a> to extract a website's <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">response code</a> for the purpose of checking if that site was accessible. Today, we'll show how to use <a href="https://www.gnu.org/software/wget/">wget</a> to achieve the same effect.</p>
<p>Here's the gist of how to do this:</p>
<pre><code>#!/usr/bin/env bash

url=&quot;https://geekberg.info&quot;

wget_check() { 

  status_code=$(wget --spider --server-response $url 2&gt;&amp;1 | awk  '/HTTP\/1.1/{print $2}' | head -1)  

  printf &quot;%s\\n&quot; &quot;$status_code&quot;

  if [ &quot;$status_code&quot; -ne &quot;200&quot; ] ; then 
    printf &quot;%s\\n&quot; &quot;BAD URL&quot; 
  else 
    printf &quot;%s\\n&quot; &quot;GOOD URL&quot; 
fi 
} 

wget_check
</code></pre>
<p>We use wget's built-in ability to function as a <a href="https://en.wikipedia.org/wiki/Web_crawler">web crawler</a> so that it checks--but does not download--a given site's headers:</p>
<pre><code>URL transformed to HTTPS due to an HSTS policy
Spider mode enabled. Check if remote file exists.
--2019-05-17 00:26:59--  https://geekberg.info/
Resolving geekberg.info (geekberg.info)... 159.65.178.169
Connecting to geekberg.info (geekberg.info)|159.65.178.169|:443... connected.
HTTP request sent, awaiting response... 
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Fri, 17 May 2019 00:26:59 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 13112
Connection: keep-alive
X-Powered-By: Express
Cache-Control: public, max-age=0
ETag: W/&quot;3338-S/XfSVjCnN/lfdMnlmt2u8WQ478&quot;
Vary: Accept-Encoding
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Length: 13112 (13K) [text/html]
</code></pre>
<p>Then, using <a href="https://www.gnu.org/software/gawk/manual/gawk.html">awk</a>, we parse out the second field of the line beginning with <code>HTTP/1.1</code> and pipe this information to the <a href="https://en.wikipedia.org/wiki/Head_(Unix)">head</a> command. This is done because websites can return multiple response codes, and we're only concerned with the first.</p>
<p>From there, we use an <a href="http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_07_02.html">if/else</a> constuct to check if the response code from the site is not equal to <code>200</code>. If that's the case, we'll call it a &quot;BAD URL&quot;, otherwise we know it's kosher, hence the &quot;GOOD URL&quot;.</p>
<p>Cheers.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>