<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Shell on Nelson Figueroa</title><link>https://nelson.cloud/categories/shell/</link><description>Recent content in Shell on Nelson Figueroa</description><image><title>Nelson Figueroa</title><url>https://nelson.cloud/apple-touch-icon.png</url><link>https://nelson.cloud/categories/shell/</link></image><language>en</language><lastBuildDate>Wed, 01 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://nelson.cloud/categories/shell/index.xml" rel="self" type="application/rss+xml"/><item><title>Terminal Colors Are Just Text</title><link>https://nelson.cloud/terminal-colors-are-just-text/?ref=rss</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://nelson.cloud/terminal-colors-are-just-text/?ref=rss</guid><description>ANSI escape codes are used to colorize text in a terminal.</description><content:encoded><![CDATA[<p>Terminal colors are determined by 

<a href="https://en.wikipedia.org/wiki/ANSI_escape_code" target="_blank" rel="noopener">ANSI escape codes</a>, which are just strings of text.</p>
<p>The ANSI escape code for a red color is <code>\033[31m</code>. If we prepend that to a string that we print out to the terminal, the string comes out red. This works regardless of the programming language. Here&rsquo;s an example in Python:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\033</span><span class="s2">[31mthis text is red&#34;</span><span class="p">)</span></span></span></code></pre></div><p>We can split out the ANSI escape code to make it easier to see the two parts:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">red_ansi_code</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\033</span><span class="s2">[31m&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">message</span> <span class="o">=</span> <span class="s2">&#34;this text is red&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">red_ansi_code</span> <span class="o">+</span> <span class="n">message</span><span class="p">)</span></span></span></code></pre></div><p>Both code snippets above produce the following output:</p>
<pre><code><span style="color:#c01c28">this text is red</span></code></pre>
<p>We can mix colors too. Here is an example with green and blue as well as red. We use the escape code <code>\033[0m</code> to terminate the colorization of text after each string.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\033</span><span class="s2">[31mred text</span><span class="se">\033</span><span class="s2">[0m </span><span class="se">\033</span><span class="s2">[32mgreen text</span><span class="se">\033</span><span class="s2">[0m </span><span class="se">\033</span><span class="s2">[34mblue text</span><span class="se">\033</span><span class="s2">[0m&#34;</span><span class="p">)</span></span></span></code></pre></div><p>We can split things out once again to make it more legible:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">red_ansi_code</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\033</span><span class="s2">[31m&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">green_ansi_code</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\033</span><span class="s2">[32m&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">blue_ansi_code</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\033</span><span class="s2">[34m&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">terminator</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\033</span><span class="s2">[0m&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">red_ansi_code</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="s2">&#34;red text &#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">terminator</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">green_ansi_code</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="s2">&#34;green text &#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">terminator</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">blue_ansi_code</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="s2">&#34;blue text&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">terminator</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div><p>The two snippets above print:</p>
<pre><code><span style="color:#c01c28">red text <span style="color:#26a269">green text <span style="color:#2a7bde">blue text</span></span></span></code></pre>
<p>Libraries exist to make this easier since ANSI codes look like nonsense among normal text. For example, there is a <code>termcolor</code> library for Python that lets you wrap text and specify a color.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># import the colored() function from the termcolor library</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">termcolor</span> <span class="kn">import</span> <span class="n">colored</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">colored</span><span class="p">(</span><span class="s2">&#34;red text&#34;</span><span class="p">,</span> <span class="s2">&#34;red&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">colored</span><span class="p">(</span><span class="s2">&#34;green text&#34;</span><span class="p">,</span> <span class="s2">&#34;green&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">colored</span><span class="p">(</span><span class="s2">&#34;blue text&#34;</span><span class="p">,</span> <span class="s2">&#34;blue&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div><p>That code has the same output as before:</p>
<pre><code><span style="color:#c01c28">red text <span style="color:#26a269">green text <span style="color:#2a7bde">blue text</span></span></span></code></pre>
<p>The <code>colored</code> function from the <code>termcolor</code> library makes it much cleaner to colorize terminal output.</p>
<p>There are many ANSI codes for colors. Here&rsquo;s a few examples of different colors:</p>
<table>
	<thead>
			<tr>
					<th>Color</th>
					<th>Code</th>
					<th>Escape String</th>
					<th>Output</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Red</td>
					<td><code>31</code></td>
					<td><code>\033[31m</code></td>
					<td><span style="color:#c01c28">red text</span></td>
			</tr>
			<tr>
					<td>Green</td>
					<td><code>32</code></td>
					<td><code>\033[32m</code></td>
					<td><span style="color:#26a269">green text</span></td>
			</tr>
			<tr>
					<td>Yellow</td>
					<td><code>33</code></td>
					<td><code>\033[33m</code></td>
					<td><span style="color:#c4a000">yellow text</span></td>
			</tr>
			<tr>
					<td>Blue</td>
					<td><code>34</code></td>
					<td><code>\033[34m</code></td>
					<td><span style="color:#2a7bde">blue text</span></td>
			</tr>
			<tr>
					<td>Magenta</td>
					<td><code>35</code></td>
					<td><code>\033[35m</code></td>
					<td><span style="color:#a347ba">magenta text</span></td>
			</tr>
			<tr>
					<td>Cyan</td>
					<td><code>36</code></td>
					<td><code>\033[36m</code></td>
					<td><span style="color:#2aa1b3">cyan text</span></td>
			</tr>
	</tbody>
</table>
<p>There are 

<a href="https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797" target="_blank" rel="noopener">so many more ANSI escape codes</a> to change the color of text, the background color, and even to underline and bold text.</p>
<p>It&rsquo;s useful to know the general look of ANSI escape codes because they may show up in other places. From personal experience, I&rsquo;ve seen these codes being printed out along with machine logs and it just looked like gibberish. Now I know that all that gibberish was actually ANSI escape codes that weren&rsquo;t being rendered. There was code somewhere that was printing colorized output without 

<a href="https://nelson.cloud/you-can-detect-if-code-is-being-run-inside-a-terminal/">checking if it was running in a terminal or not</a>.</p>
<p>Here&rsquo;s an example of raw ANSI codes being printed out from a Rust build with warnings. Take this Rust code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">year</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2026</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="fm">println!</span><span class="p">(</span><span class="s">&#34;hello world&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>If we try to build it with <code>cargo</code>, we get a warning due to the unused <code>year</code> variable. We can build it with the <code>--color=always</code> option to force colorization. Then we can write the warning to <code>build.log</code> and print out <code>build.log</code> with <code>cat -v</code> so that it reveals raw ANSI codes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> cargo build --color<span class="o">=</span>always <span class="p">&amp;</span>&gt; build.log
</span></span><span class="line"><span class="cl"><span class="gp">$</span> cat -v build.log
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">^[[1m^[[33mwarning^[[0m^[[1m: unused variable: `year`^[[0m
</span></span></span><span class="line"><span class="cl"><span class="go"> ^[[1m^[[94m--&gt; ^[[0msrc/main.rs:2:9
</span></span></span><span class="line"><span class="cl"><span class="go">  ^[[1m^[[94m|^[[0m
</span></span></span><span class="line"><span class="cl"><span class="go">^[[1m^[[94m2^[[0m ^[[1m^[[94m|^[[0m     let year = 2026;
</span></span></span><span class="line"><span class="cl"><span class="go">  ^[[1m^[[94m|^[[0m         ^[[1m^[[33m^^^^^[[0m ^[[1m^[[33mhelp: if this is intentional, prefix it with an underscore: `_year`^[[0m
</span></span></span><span class="line"><span class="cl"><span class="go">  ^[[1m^[[94m|^[[0m
</span></span></span><span class="line"><span class="cl"><span class="go">  ^[[1m^[[94m= ^[[0m^[[1mnote^[[0m: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">^[[1m^[[33mwarning^[[0m: `ansidemo` (bin &#34;ansidemo&#34;) generated 1 warning (run `cargo fix --bin &#34;ansidemo&#34; -p ansidemo` to apply 1 suggestion)
</span></span></span><span class="line"><span class="cl"><span class="go">^[[1m^[[92m    Finished^[[0m `dev` profile [unoptimized + debuginfo] target(s) in 0.10s
</span></span></span></code></pre></div><p>Try running both <code>cat build.log</code> and <code>cat -v build.log</code> to see the colorized and non-colorized (with raw ANSI codes) outputs side by side.</p>
]]></content:encoded></item><item><title>You Can Detect if Code Is Being Run Inside a Terminal</title><link>https://nelson.cloud/you-can-detect-if-code-is-being-run-inside-a-terminal/?ref=rss</link><pubDate>Sun, 28 Jun 2026 00:00:00 +0000</pubDate><guid>https://nelson.cloud/you-can-detect-if-code-is-being-run-inside-a-terminal/?ref=rss</guid><description>Most programming languages have a way to detect if code is being run in a terminal.</description><content:encoded><![CDATA[<p>I learned about the <code>sys.stdout.isatty()</code> method in Python which allows you to detect if your code is running in a terminal. For example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">isatty</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;This program is running in a terminal!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;This program is running somewhere else, like a CI/CD pipeline&#34;</span><span class="p">)</span></span></span></code></pre></div><p>Running the program outputs the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> python3 tty.py
</span></span><span class="line"><span class="cl"><span class="go">This program is running in a terminal!
</span></span></span></code></pre></div><p>But if we pipe the program into some other tool, like <code>cat</code>, it is no longer running in a terminal and prints the other message:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> python3 tty.py <span class="p">|</span> cat
</span></span><span class="line"><span class="cl"><span class="go">This program is running somewhere else, like a CI/CD pipeline
</span></span></span></code></pre></div><p>This is why some colorized output from code is not colorized in CI/CD pipelines. The code itself checks if it&rsquo;s being run in a terminal, and if not, it doesn&rsquo;t display colors that would result in junk being displayed due to the nature of 

<a href="https://en.wikipedia.org/wiki/ANSI_escape_code#Colors" target="_blank" rel="noopener">ANSI escape codes</a> used in colorization. I always thought that it was the CI/CD systems that suppressed color output but that&rsquo;s not necessarily the case.</p>
<p>This capability is not unique to Python. Other languages support this too.</p>
<p>In JavaScript:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">stdout</span><span class="p">.</span><span class="nx">isTTY</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;This program is running in a terminal!&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;This program is running somewhere else, like a CI/CD pipeline&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>In Ruby:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">if</span> <span class="vg">$stdout</span><span class="o">.</span><span class="n">tty?</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="s2">&#34;This program is running in a terminal!&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="s2">&#34;This program is running somewhere else, like a CI/CD pipeline&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div><p>In Bash:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -t <span class="m">1</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;This program is running in a terminal!&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;This program is running somewhere else, like a CI/CD pipeline&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span></span></span></code></pre></div><p>Go doesn&rsquo;t have a built-in method to detect if code is being run inside a terminal, so we need to import the <code>x/term</code> package:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;os&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;golang.org/x/term&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">term</span><span class="p">.</span><span class="nf">IsTerminal</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">.</span><span class="nf">Fd</span><span class="p">()))</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;This program is running in a terminal!&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;This program is running somewhere else, like a CI/CD pipeline&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>]]></content:encoded></item><item><title>Creating High Quality GIFs from Asciinema Recordings</title><link>https://nelson.cloud/creating-high-quality-gifs-from-asciinema-recordings/?ref=rss</link><pubDate>Sat, 04 Oct 2025 00:00:00 +0000</pubDate><guid>https://nelson.cloud/creating-high-quality-gifs-from-asciinema-recordings/?ref=rss</guid><description>Use &lt;code&gt;agg&lt;/code&gt; with a huge font size to get high quality GIFs.</description><content:encoded><![CDATA[<p>Say you have a recording created with 

<a href="https://asciinema.org/" target="_blank" rel="noopener">Asciinema</a> named <code>recording.cast</code> and now you want to convert that into a GIF. You can use the <code>agg</code> command (which is a 

<a href="https://docs.asciinema.org/manual/agg/installation/" target="_blank" rel="noopener">separate installation</a>) to convert it like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">agg recording.cast recording.gif</span></span></code></pre></div><p>However, if you run <code>agg</code> with the default settings the resulting GIF won&rsquo;t be high quality and it&rsquo;ll look a little fuzzy.</p>
<p>To get a high quality GIF you need to specify a big font size with the <code>--font-size</code> option:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">agg --font-size <span class="m">64</span> recording.cast recording.gif</span></span></code></pre></div><p>Here are two GIFs you can compare. The top one was created with the default <code>agg</code> settings. The bottom one was created with <code>--font-size 64</code>. Depending on your display, you may need to zoom in a bit to see the difference:</p>
<img src="/asciinema-high-quality-gifs/pulumi-up.gif" alt="fuzzy GIF created with default agg settings" width="720" height="474" style="max-width: 100%; height: auto; aspect-ratio: 772 / 509;" loading="lazy" decoding="async">
<img src="/asciinema-high-quality-gifs/pulumi-up-in-hd.gif" alt="higher quality GIF created with agg --font-size 64" width="720" height="475" style="max-width: 100%; height: auto; aspect-ratio: 3532 / 2329;" loading="lazy" decoding="async">
<p>It&rsquo;s a noticeable difference. The trade-off here is that the higher quality GIF will be a bigger filesize, so be mindful of that. Try experimenting with different font sizes.</p>
<p>I was expecting there to be some sort of <code>--size</code> or <code>--quality</code> option, but you just need to increase the font to get higher quality GIFs with <code>agg</code>.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://docs.asciinema.org/manual/agg/" target="_blank" rel="noopener">https://docs.asciinema.org/manual/agg/</a></li>
<li>

<a href="https://docs.asciinema.org/manual/agg/installation/" target="_blank" rel="noopener">https://docs.asciinema.org/manual/agg/installation/</a></li>
<li>

<a href="https://docs.asciinema.org/manual/agg/usage/" target="_blank" rel="noopener">https://docs.asciinema.org/manual/agg/usage/</a></li>
</ul>
]]></content:encoded></item><item><title>Quick Tip: Mute the Terminal Login Message with a .hushlogin File</title><link>https://nelson.cloud/quick-tip-mute-the-terminal-login-message-with-a-.hushlogin-file/?ref=rss</link><pubDate>Sun, 28 Sep 2025 00:00:00 +0000</pubDate><guid>https://nelson.cloud/quick-tip-mute-the-terminal-login-message-with-a-.hushlogin-file/?ref=rss</guid><description>Create a .hushlogin file in your home directory to silence login messages.</description><content:encoded><![CDATA[<p>Today I learned that you can create an empty <code>.hushlogin</code> file in your home directory on macOS and Linux to hide the login message you get when starting up a new terminal window or tab.</p>
<p>I tried this on macOS but it should work on Linux too.</p>
<p>For example, when I start up a new terminal window/tab I see the following message:</p>
<img src="/mute-terminal-login-message/before.webp" alt="terminal with login message" width="980" height="254" style="max-width: 100%; height: auto; aspect-ratio: 980 / 254;" loading="lazy" decoding="async">
<p>After I create the <code>.hushlogin</code> file in my home directory, the login message goes away. First I created the file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">touch ~/.hushlogin</span></span></code></pre></div><p>Then I opened a new terminal window to verify that the message no longer shows up:</p>
<img src="/mute-terminal-login-message/after.webp" alt="terminal without login message after creating .hushlogin file" width="980" height="254" style="max-width: 100%; height: auto; aspect-ratio: 980 / 254;" loading="lazy" decoding="async">
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://stackoverflow.com/questions/15769615/remove-last-login-message-for-new-tabs-in-terminal" target="_blank" rel="noopener">https://stackoverflow.com/questions/15769615/remove-last-login-message-for-new-tabs-in-terminal</a></li>
<li>

<a href="https://www.cyberciti.biz/howto/turn-off-the-login-banner-in-linux-unix-with-hushlogin-file/" target="_blank" rel="noopener">https://www.cyberciti.biz/howto/turn-off-the-login-banner-in-linux-unix-with-hushlogin-file/</a></li>
</ul>
]]></content:encoded></item><item><title>How to Clone a Specific Git Branch Without Other Branches</title><link>https://nelson.cloud/how-to-clone-a-specific-git-branch-without-other-branches/?ref=rss</link><pubDate>Thu, 27 Feb 2025 00:00:00 +0000</pubDate><guid>https://nelson.cloud/how-to-clone-a-specific-git-branch-without-other-branches/?ref=rss</guid><description>Clone a single Git branch using &amp;ndash;single-branch and &amp;ndash;depth for faster cloning.</description><content:encoded><![CDATA[<h2 id="cloning-a-specific-branch">Cloning a Specific Branch</h2>
<p>To clone a specific branch of a git repository without cloning all other branches, use the following command formula:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git clone --single-branch --branch &lt;branch_name&gt; &lt;repo_URL.git&gt;</span></span></code></pre></div><p>For example, if you want to clone the <code>release-1.28</code> branch of the 

<a href="https://github.com/kubernetes/kubernetes/tree/release-1.28" target="_blank" rel="noopener">Kubernetes GitHub repository</a>, run:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git clone --single-branch --branch release-1.28 https://github.com/kubernetes/kubernetes.git</span></span></code></pre></div><h2 id="cloning-the-latest-commit-of-a-specific-branch">Cloning the Latest Commit of a Specific Branch</h2>
<p>If you only want to clone the latest commit of a specific branch (which results in a faster and smaller cloning operation) use <code>--depth 1</code>. The command formula looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git clone --single-branch --branch &lt;branch_name&gt; --depth <span class="m">1</span> &lt;repo_URL.git&gt;</span></span></code></pre></div><p>And here is another example using the <code>release-1.28</code> branch of the 

<a href="https://github.com/kubernetes/kubernetes/tree/release-1.28" target="_blank" rel="noopener">Kubernetes GitHub repository</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git clone --single-branch --branch release-1.28 --depth <span class="m">1</span> https://github.com/kubernetes/kubernetes.git</span></span></code></pre></div><h2 id="references">References</h2>
<ul>
<li>

<a href="https://www.freecodecamp.org/news/git-clone-branch-how-to-clone-a-specific-branch/" target="_blank" rel="noopener">https://www.freecodecamp.org/news/git-clone-branch-how-to-clone-a-specific-branch/</a></li>
<li>

<a href="https://git-scm.com/docs/git-clone" target="_blank" rel="noopener">https://git-scm.com/docs/git-clone</a></li>
</ul>
]]></content:encoded></item><item><title>Delete All Pulumi Stacks with One Command</title><link>https://nelson.cloud/delete-all-pulumi-stacks-with-one-command/?ref=rss</link><pubDate>Tue, 18 Feb 2025 00:00:00 +0000</pubDate><guid>https://nelson.cloud/delete-all-pulumi-stacks-with-one-command/?ref=rss</guid><description>How to delete all Pulumi stacks with a shell one-liner.</description><content:encoded><![CDATA[<p>There currently isn&rsquo;t a way to delete all stacks with <code>pulumi stack rm</code> so this is an alternative way to achieve that.</p>
<h2 id="delete-all-stacks-in-the-current-project">Delete All Stacks in the Current Project</h2>
<p>To delete all Pulumi stacks in the current Pulumi project you can run the following command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pulumi stack ls <span class="p">|</span> tail -n +2 <span class="p">|</span> tr -d <span class="s2">&#34;*&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{print $1}&#39;</span> <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> -r stack<span class="p">;</span> <span class="k">do</span> pulumi stack rm -y <span class="s2">&#34;</span><span class="nv">$stack</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">done</span><span class="p">;</span></span></span></code></pre></div><h2 id="delete-all-stacks-across-all-projects">Delete All Stacks Across All Projects</h2>
<blockquote><p><strong>Warning:</strong></p>It should go without saying but, <strong>be careful when doing this</strong>.</blockquote>

<p>To delete all Pulumi stacks across all Pulumi projects we need to use <code>pulumi stack ls -a</code> instead of <code>pulumi stack ls</code>. So the full command is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pulumi stack ls -a <span class="p">|</span> tail -n +2 <span class="p">|</span> tr -d <span class="s2">&#34;*&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{print $1}&#39;</span> <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> -r stack<span class="p">;</span> <span class="k">do</span> pulumi stack rm -y <span class="s2">&#34;</span><span class="nv">$stack</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">done</span><span class="p">;</span></span></span></code></pre></div><h2 id="command-breakdown">Command Breakdown</h2>
<p>List pulumi stacks (use <code>-a</code> option for all stacks across all projects):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pulumi stack ls</span></span></code></pre></div><p>Start at the second line of the previous output:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">tail -n +2</span></span></code></pre></div><p>Delete all occurrences of <code>*</code>. There is a <code>*</code> character next to the currently selected stack and we need to remove this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">tr -d <span class="s2">&#34;*&#34;</span></span></span></code></pre></div><p>Print only the first column:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">awk <span class="s1">&#39;{print $1}&#39;</span></span></span></code></pre></div><p>This is a loop in one-liner format. It reads the previous output line by line and assigns each line to a string variable called <code>stack</code>, then runs the command <code>pulumi stack rm -y</code> on each stack.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="k">while</span> <span class="nb">read</span> -r stack<span class="p">;</span> <span class="k">do</span> pulumi stack rm -y <span class="s2">&#34;</span><span class="nv">$stack</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">done</span><span class="p">;</span></span></span></code></pre></div><h2 id="references">References</h2>
<ul>
<li>

<a href="https://stackoverflow.com/questions/7318497/omitting-the-first-line-from-any-linux-command-output" target="_blank" rel="noopener">https://stackoverflow.com/questions/7318497/omitting-the-first-line-from-any-linux-command-output</a></li>
<li>

<a href="https://www.pulumi.com/docs/iac/cli/commands/pulumi_stack_list/" target="_blank" rel="noopener">https://www.pulumi.com/docs/iac/cli/commands/pulumi_stack_list/</a></li>
<li>

<a href="https://www.pulumi.com/docs/iac/cli/commands/pulumi_stack_remove/" target="_blank" rel="noopener">https://www.pulumi.com/docs/iac/cli/commands/pulumi_stack_remove/</a></li>
</ul>
]]></content:encoded></item><item><title>AWS SSM: Run a Shell Command Against Multiple EC2 Instances</title><link>https://nelson.cloud/aws-ssm-run-a-shell-command-against-multiple-ec2-instances/?ref=rss</link><pubDate>Thu, 18 Jan 2024 00:00:00 +0000</pubDate><guid>https://nelson.cloud/aws-ssm-run-a-shell-command-against-multiple-ec2-instances/?ref=rss</guid><description>How to run a shell command against multiple EC2 instances using AWS Systems Manager</description><content:encoded><![CDATA[<p>In AWS, you can use 

<a href="https://aws.amazon.com/systems-manager/" target="_blank" rel="noopener">AWS Systems Manager</a> to run commands on any EC2 Instances that have the 

<a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html" target="_blank" rel="noopener">AWS Systems Manager Agent</a> installed. AWS Systems Manager has a lot more functionality than what I&rsquo;m demonstrating here, but this is a specific use case.</p>
<h2 id="shell-command-setup">Shell Command Setup</h2>
<p>To get started running a shell command on multiple EC2 Instances, head over to 

<a href="https://console.aws.amazon.com/systems-manager/home" target="_blank" rel="noopener">AWS Systems Manager via the AWS Console</a>. Then, on the left side under &ldquo;Node Management&rdquo;, click on &ldquo;Run Command&rdquo;.</p>
<img src="/run-a-shell-command-on-ec2-instances/node-management.webp" alt="Node Management menu" width="500" height="608" style="max-width: 100%; height: auto; aspect-ratio: 500 / 608;" loading="lazy" decoding="async">
<p>On the next screen, click on the orange &ldquo;Run a Command&rdquo; button on the right.</p>
<img src="/run-a-shell-command-on-ec2-instances/aws-ssm-run-command.webp" alt="AWS SSM home page" width="720" height="214" style="max-width: 100%; height: auto; aspect-ratio: 2040 / 608;" loading="lazy" decoding="async">
<p>Next, under &ldquo;Command document&rdquo; search for &ldquo;shell&rdquo; to find <code>AWS-RunShellScript</code> and select it.</p>
<img src="/run-a-shell-command-on-ec2-instances/command-document.webp" alt="AWS SSM command document" width="720" height="328" style="max-width: 100%; height: auto; aspect-ratio: 2704 / 1232;" loading="lazy" decoding="async">
<p>Then, under &ldquo;Command parameters&rdquo; type in the shell command you want to run on your EC2 instances. In my case, I want to run security updates on my instances.</p>
<img src="/run-a-shell-command-on-ec2-instances/command-parameters.webp" alt="AWS SSM command parameters" width="720" height="330" style="max-width: 100%; height: auto; aspect-ratio: 984 / 452;" loading="lazy" decoding="async">
<p>Then select EC2 Instances by your preferred method under &ldquo;Target Selection&rdquo;. I chose to manually select my instances but you can also select based on tags or resource groups. Make sure you are using the same region your EC2 instances are in!</p>
<img src="/run-a-shell-command-on-ec2-instances/target-selection.webp" alt="AWS SSM target selection" width="720" height="333" style="max-width: 100%; height: auto; aspect-ratio: 2692 / 1248;" loading="lazy" decoding="async">
<p>This part is optional. If you want log outputs of the commands that are run on every instance you can save logs to an S3 Bucket under &ldquo;Output options&rdquo;. Make sure the Instance Profile IAM Role has permissions to write to this bucket!</p>
<img src="/run-a-shell-command-on-ec2-instances/output-options.webp" alt="AWS SSM output options" width="720" height="269" style="max-width: 100%; height: auto; aspect-ratio: 2688 / 1006;" loading="lazy" decoding="async">
<p>I left all other settings with their default values.</p>
<p>Now we can click the orange &ldquo;Run&rdquo; button on the bottom of the page to execute our command.</p>
<h2 id="after-running-a-command">After Running a Command</h2>
<p>After running a command you&rsquo;ll see the following screen. You can see the status of the command execution on each EC2 instance to ensure everything went fine. You can also see the Command ID here which will come in handy if you enabled S3 logging.</p>
<img src="/run-a-shell-command-on-ec2-instances/command-id.webp" alt="SSM screen after execution" width="720" height="261" style="max-width: 100%; height: auto; aspect-ratio: 2708 / 984;" loading="lazy" decoding="async">
<h3 id="viewing-logs">Viewing Logs</h3>
<p>If you enabled logging to S3, you can browse to the bucket you selected and view logs. The output log file will be several directories deep under <code>&lt;Command ID&gt;/&lt;ec2-instance-id&gt;/&lt;command document&gt;/...</code>. It&rsquo;s easier if you refer to the screenshot below:</p>
<img src="/run-a-shell-command-on-ec2-instances/s3-logs.webp" alt="AWS SSM output logs" width="720" height="324" style="max-width: 100%; height: auto; aspect-ratio: 2444 / 1100;" loading="lazy" decoding="async">
<p>I downloaded the <code>stdout</code> file locally and viewed it via the command line to confirm that my command executed properly on this particular EC2 instance:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> cat stdout
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Last metadata expiration check: 0:15:25 ago on Thu Jan 18 21:10:06 2024.
</span></span></span><span class="line"><span class="cl"><span class="go">Dependencies resolved.
</span></span></span><span class="line"><span class="cl"><span class="go">Nothing to do.
</span></span></span><span class="line"><span class="cl"><span class="go">Complete!
</span></span></span></code></pre></div><h2 id="troubleshooting">Troubleshooting</h2>
<p>I personally ran into some issues when trying to do this for the first time. Here are a few troubleshooting solutions.</p>
<h3 id="ec2-instances-dont-show-up-in-systems-manager">EC2 Instances Don&rsquo;t Show Up in Systems Manager</h3>
<p>If your EC2 Instances do not show up under &ldquo;Target selection&rdquo; they may not have the SSM agent installed. AWS has documentation to guide you through the installation process: 

<a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-manual-agent-install.html" target="_blank" rel="noopener">Manually installing SSM Agent on EC2 instances for Linux</a>.</p>
<p>If your EC2 Instances do have the SSM agent installed but still aren&rsquo;t showing up, they may not have an IAM instance profile. You can confirm this by clicking on an EC2 Instance and then checking under the &ldquo;IAM Role&rdquo; field. If this field is empty, assign an IAM Role and ensure it has proper permissions. In my case, I used two AWS managed policies <code>AmazonSSMManagedInstanceCore</code> and <code>AmazonSSMPatchAssociation</code>.</p>
<img src="/run-a-shell-command-on-ec2-instances/permission-policies.webp" alt="AWS IAM permission policies" width="720" height="299" style="max-width: 100%; height: auto; aspect-ratio: 1500 / 624;" loading="lazy" decoding="async">
<p>(The inline policy in the screenshot is to grant this Role S3 permissions for logging purposes which I cover in the next section.)</p>
<h3 id="no-log-output-to-s3">No Log Output to S3</h3>
<p>If you see no log output in your S3 bucket, you may need to add proper permissions to the instance profile IAM Role of the EC2 instances you selected.</p>
<p>Here is the policy I attached to the IAM instance profile role that got logging to work for me. Change the bucket name <code>my-bucket</code> accordingly!</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Version&#34;</span><span class="p">:</span> <span class="s2">&#34;2012-10-17&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Statement&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;s3:PutObject&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;s3:GetBucketAcl&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;s3:GetBucketLocation&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;s3:GetObject&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;s3:ListBucket&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Resource&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;arn:aws:s3:::my-bucket&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;arn:aws:s3:::my-bucket/*&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>]]></content:encoded></item><item><title>Delete All node_modules Directories Recursively in macOS and Linux</title><link>https://nelson.cloud/delete-all-node_modules-directories-recursively-in-macos-and-linux/?ref=rss</link><pubDate>Sun, 14 May 2023 00:00:00 +0000</pubDate><guid>https://nelson.cloud/delete-all-node_modules-directories-recursively-in-macos-and-linux/?ref=rss</guid><description>How to delete all node_modules directories recursively in macOS and Linux systems.</description><content:encoded><![CDATA[<p>We can use the <code>find</code> command to delete a specific directory recursively. This is the command formula:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">find /path/to/starting/directory/ -type d -name <span class="s2">&#34;directory_to_delete&#34;</span> -exec rm -rf <span class="o">{}</span> <span class="se">\;</span></span></span></code></pre></div><p>For example, if we wanted to delete all <code>node_modules</code> directories within the path <code>/projects/javascript/</code>, we would run:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">find /projects/javascript/ -type d -name <span class="s2">&#34;node_modules&#34;</span> -exec rm -rf <span class="o">{}</span> <span class="se">\;</span></span></span></code></pre></div><blockquote><p><strong>Note:</strong></p><p>Sometimes the output says:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">find: ./node_modules: No such file or directory</span></span></code></pre></div><p>But the command should still work.</p>
</blockquote>

<p>If you look closely, we&rsquo;re really just executing a command against all <code>node_modules</code> directories in the <code>/projects/javascript/</code> directory (<code>rm -rf</code>). This command can be modified to execute other commands against all <code>node_modules</code> directories, but that is out of the scope of this post :)</p>
]]></content:encoded></item><item><title>Scrape Contributor Emails From Any Git Repository</title><link>https://nelson.cloud/scrape-contributor-emails-from-any-git-repository/?ref=rss</link><pubDate>Thu, 21 Jul 2022 00:00:00 +0000</pubDate><guid>https://nelson.cloud/scrape-contributor-emails-from-any-git-repository/?ref=rss</guid><description>Scraping contributor emails from git repositories using git shortlog.</description><content:encoded><![CDATA[<p>In a 

<a href="https://nelson.cloud/scraping-github-contributor-emails/">previous post</a> I wrote about how it&rsquo;s possible to scrape emails from GitHub repositories using their API.
I even wrote up a 

<a href="https://github.com/nelsonfigueroa/github-email-scraper" target="_blank" rel="noopener">Ruby script</a> to do this.
I now realize that is a very complicated way to go about it after discovering the <code>git shortlog</code> command.</p>
<p>With <code>git shortlog</code> you can list all contributor emails for any git repository, not just GitHub repos.</p>
<blockquote><p><strong>Disclaimer:</strong></p>I am writing about this to make others aware of this form of scraping and it is purely for educational purposes. I do not plan on doing anything with emails from git repos and you shouldn’t either.</blockquote>

<blockquote><p><strong>tl;dr:</strong></p><p>You can run this command within any git repo to extract all contributor emails:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git shortlog -sea <span class="p">|</span> grep -E -o <span class="s2">&#34;\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{print tolower($0)}&#39;</span> <span class="p">|</span> sort <span class="p">|</span> uniq <span class="p">|</span> grep -wv <span class="s1">&#39;users.noreply.github.com&#39;</span></span></span></code></pre></blockquote>

<h2 id="command-break-down">Command Break Down</h2>
<p>The <code>git shortlog -sea</code> part of the command is short for <code>git shortlog --summary --email --all</code>. This command outputs the number of commits each user has made, along with their name and email, across all branches.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> git shortlog -sea
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">    54  First Last &lt;FirstLast@example.com&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">   385  Another User &lt;Anotheruser@example.com&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">     2  user1 &lt;user1@example.com&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">    31  first last &lt;firstlast@example.com&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">    10  Someone Else &lt;1234567+someoneelse@users.noreply.github.com&gt;
</span></span></span></code></pre></div><p>The next command, <code>grep -E -o &quot;\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b&quot;</code>, extracts emails from each line using a regular expression.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> git shortlog -sea <span class="p">|</span> grep -E -o <span class="s2">&#34;\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b&#34;</span>
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">FirstLast@example.com
</span></span></span><span class="line"><span class="cl"><span class="go">Anotheruser@example.com
</span></span></span><span class="line"><span class="cl"><span class="go">user1@example.com
</span></span></span><span class="line"><span class="cl"><span class="go">firstlast@example.com
</span></span></span><span class="line"><span class="cl"><span class="go">1234567+someoneelse@users.noreply.github.com
</span></span></span></code></pre></div><p>The output from the previous command is piped into <code>awk '{print tolower($0)}'</code>, which lowercases all the emails. Sometimes emails are typed in with capital letters. Lowercasing all characters will help with sorting and finding unique emails later.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> git shortlog -sea <span class="p">|</span> grep -E -o <span class="s2">&#34;\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{print tolower($0)}&#39;</span>
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">firstlast@example.com
</span></span></span><span class="line"><span class="cl"><span class="go">anotheruser@example.com
</span></span></span><span class="line"><span class="cl"><span class="go">user1@example.com
</span></span></span><span class="line"><span class="cl"><span class="go">firstlast@example.com
</span></span></span><span class="line"><span class="cl"><span class="go">1234567+someoneelse@users.noreply.github.com
</span></span></span></code></pre></div><p>After that, the output is piped into <code>sort</code> and <code>uniq</code>. These commands are straightforward. The emails are sorted alphabetically, then duplicates are excluded from the output.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> git shortlog -sea <span class="p">|</span> grep -E -o <span class="s2">&#34;\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{print tolower($0)}&#39;</span> <span class="p">|</span> sort <span class="p">|</span> uniq
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">1234567+someoneelse@users.noreply.github.com
</span></span></span><span class="line"><span class="cl"><span class="go">anotheruser@example.com
</span></span></span><span class="line"><span class="cl"><span class="go">firstlast@example.com
</span></span></span><span class="line"><span class="cl"><span class="go">user1@example.com
</span></span></span></code></pre></div><p>That should suffice for a lot of git repos, but I also added <code>grep -wv 'users.noreply.github.com'</code> to the end of the command to exclude noreply emails associated with GitHub.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> git shortlog -sea <span class="p">|</span> grep -E -o <span class="s2">&#34;\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{print tolower($0)}&#39;</span> <span class="p">|</span> sort <span class="p">|</span> uniq <span class="p">|</span> grep -wv <span class="s1">&#39;users.noreply.github.com&#39;</span>
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">anotheruser@example.com
</span></span></span><span class="line"><span class="cl"><span class="go">firstlast@example.com
</span></span></span><span class="line"><span class="cl"><span class="go">user1@example.com
</span></span></span></code></pre></div><h2 id="extracting-emails-with-git-log">Extracting Emails With <code>git log</code></h2>
<p>It&rsquo;s possible to do something similar with the <code>git log --pretty=&quot;%ce&quot;</code> command. However, I noticed that this command does not show as many emails as <code>git shortlog</code>. I didn&rsquo;t look too much into it, but I believe it only pulls emails from one branch rather than all branches like with <code>git shortlog --all</code>.</p>
<h2 id="references">References</h2>
<p>I learned about <code>git shortlog</code> from this Stack Overflow question:</p>
<ul>
<li>

<a href="https://stackoverflow.com/questions/9597410/list-all-developers-on-a-project-in-git" target="_blank" rel="noopener">https://stackoverflow.com/questions/9597410/list-all-developers-on-a-project-in-git</a></li>
</ul>
<p>I got the email regex from here:</p>
<ul>
<li>

<a href="https://www.shellhacks.com/regex-find-email-addresses-file-grep/" target="_blank" rel="noopener">https://www.shellhacks.com/regex-find-email-addresses-file-grep/</a></li>
</ul>
]]></content:encoded></item><item><title>Cleaning Up Residual Files on macOS After Deleting Apps</title><link>https://nelson.cloud/cleaning-up-residual-files-on-macos-after-deleting-apps/?ref=rss</link><pubDate>Sun, 28 Feb 2021 00:00:00 +0000</pubDate><guid>https://nelson.cloud/cleaning-up-residual-files-on-macos-after-deleting-apps/?ref=rss</guid><description>Clean up residual files and directories after deleting macOS apps.</description><content:encoded><![CDATA[<p>After deleting apps on macOS, they tend to leave behind residual files and directories throughout the system. You can use the <code>find</code> command to find these files after an app has been deleted. I&rsquo;ll be deleting the LastPass app and removing its residual files as an example.</p>
<blockquote><p><strong>2024-10-13 Update:</strong></p><p>I recently discovered 

<a href="https://github.com/alienator88/Pearcleaner" target="_blank" rel="noopener">Pearcleaner</a>. You can use this app to delete other apps along with all the extra files and folders they create. I recommend you use this first and then continue reading this post if you want to look more deeply.</p>
<p>You can download Pearcleaner from the link above or install it with 

<a href="https://formulae.brew.sh/cask/pearcleaner" target="_blank" rel="noopener">Homebrew</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">brew install --cask pearcleaner</span></span></code></pre></blockquote>

<h2 id="searching-for-residual-files-and-directories">Searching for Residual Files and Directories</h2>
<p>To find directories and files related to LastPass, I ran a system-wide search using <code>find</code>. I excluded directories such as <code>/System/Volumes/Data</code> since those result in errors like &ldquo;Operation not permitted&rdquo;. I also excluded Homebrew directories that don&rsquo;t need to be cleaned up. You can add more directories as needed, just make sure to not add a trailing slash to the filepaths!</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">find / <span class="se">\
</span></span></span><span class="line"><span class="cl">-not <span class="se">\(</span> -path /System/Volumes/Data -prune <span class="se">\)</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">-not <span class="se">\(</span> -path /usr/local/Homebrew -prune <span class="se">\)</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">-not <span class="se">\(</span> -path /usr/local/Cellar -prune <span class="se">\)</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">-not <span class="se">\(</span> -path /usr/local/Caskroom -prune <span class="se">\)</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">-name <span class="se">\*</span>lastpass<span class="se">\*</span> 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> grep -v -E <span class="s1">&#39;Operation not permitted|Permission denied|Not a directory&#39;</span></span></span></code></pre></div><p>The command may take some time to complete depending on your machine specs and amount of files you have. It took around ~10 minutes for me on a 2019 MacBook Pro with an i7 Intel CPU.</p>
<p>Here&rsquo;s the output I got from the command above. I can now go through each of these files and folders and decide if I want to delete them manually:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/private/var/folders/m9/_7bg6tbn3636m1zzlzq33bwh0000gn/T/com.lastpass.lastpassmacdesktop
</span></span><span class="line"><span class="cl">/private/var/folders/m9/_7bg6tbn3636m1zzlzq33bwh0000gn/C/com.lastpass.lastpassmacdesktop
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Application Support/com.lastpass.lastpassmacdesktop
</span></span><span class="line"><span class="cl">/Users/nelson/Library/WebKit/com.lastpass.lastpassmacdesktop
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Preferences/com.lastpass.lastpassmacdesktop.plist
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Application Scripts/N24REP3BMN.lmi.lastpass.group
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Application Scripts/com.lastpass.lastpassmacdesktop.safariext
</span></span><span class="line"><span class="cl">/Users/nelson/Library/HTTPStorages/com.lastpass.lastpassmacdesktop.binarycookies
</span></span><span class="line"><span class="cl">/Users/nelson/Library/HTTPStorages/com.lastpass.lastpassmacdesktop
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Group Containers/N24REP3BMN.lmi.lastpass.group
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Group Containers/N24REP3BMN.lmi.lastpass.group/Library/Preferences/N24REP3BMN.lmi.lastpass.group.plist
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Group Containers/N24REP3BMN.lmi.lastpass.group/Library/Application Scripts/N24REP3BMN.lmi.lastpass.group
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Containers/com.lastpass.LastPass
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Containers/com.lastpass.lastpassmacdesktop.safariext
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Containers/com.lastpass.lastpassmacdesktop.safariext/Data/Library/Application Scripts/com.lastpass.lastpassmacdesktop.safariext
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Caches/com.crashlytics.data/com.lastpass.lastpassmacdesktop
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Caches/Homebrew/Cask/lastpass--4.116.0.dmg
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Caches/com.lastpass.lastpassmacdesktop</span></span></code></pre></div><p>This could probably be automated all the way through the deletion step, but I prefer to double check what is actually going to be deleted.</p>
<p>Also, this <em>may</em> help to free up space on macOS, but I mainly did it because I like keeping my system tidy.</p>
<p>Try this out with whatever apps you&rsquo;ve deleted in the past. You can also try finding files and folders by specifying a company name. For example, replace <code>\*lastpass\*</code> with <code>\*microsoft\*</code> and see what you get!</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://stackoverflow.com/questions/4210042/how-do-i-exclude-a-directory-when-using-find" target="_blank" rel="noopener">https://stackoverflow.com/questions/4210042/how-do-i-exclude-a-directory-when-using-find</a></li>
</ul>
]]></content:encoded></item></channel></rss>