<?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/opengraph-images/default.png</url><link>https://nelson.cloud/opengraph-images/default.png</link></image><language>en</language><lastBuildDate>Wed, 01 Apr 2026 22:42:16 -0700</lastBuildDate><atom:link href="https://nelson.cloud/categories/shell/index.xml" rel="self" type="application/rss+xml"/><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Shell</span><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_ls/" target="_blank" rel="noopener">https://www.pulumi.com/docs/iac/cli/commands/pulumi_stack_ls/</a></li>
<li>

<a href="https://www.pulumi.com/docs/iac/cli/commands/pulumi_stack_rm/" target="_blank" rel="noopener">https://www.pulumi.com/docs/iac/cli/commands/pulumi_stack_rm/</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"><span class="code-lang">Console</span><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"><span class="code-lang">JSON</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Text</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Console</span><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"><span class="code-lang">Console</span><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"><span class="code-lang">Console</span><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"><span class="code-lang">Console</span><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"><span class="code-lang">Console</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Shell</span><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"><span class="code-lang">Text</span><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>