<?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>Bash on Nicola Iarocci</title>
    <link>https://nicolaiarocci.com/tags/bash/</link>
    <description>Recent content in Bash on Nicola Iarocci</description>
    <generator>Hugo -- 0.143.1</generator>
    <language>en</language>
    <copyright>Produced / Written / Maintained by Nicola Iarocci since 2010</copyright>
    <lastBuildDate>Fri, 21 Nov 2025 16:57:44 +0100</lastBuildDate>
    <atom:link href="https://nicolaiarocci.com/tags/bash/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Curl and jq go to a conference</title>
      <link>https://nicolaiarocci.com/curl-and-jq-go-to-a-conference/</link>
      <pubDate>Fri, 21 Nov 2025 16:57:44 +0100</pubDate>
      <guid>https://nicolaiarocci.com/curl-and-jq-go-to-a-conference/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m presenting at the &lt;a href=&#34;https://www.wpc.education&#34;&gt;WPC 2025 Conference&lt;/a&gt; on December 3rd in Milan. My session topic is Feature Flag Management and Dynamic Configurations in C#.&lt;/p&gt;
&lt;p&gt;I will use a Web API as an example project, and since I&amp;rsquo;ll be using curl live to query it, I&amp;rsquo;ll need to pipe responses through &lt;a href=&#34;https://jqlang.org&#34;&gt;jq&lt;/a&gt; to obtain nicely formatted JSON for the audience.&lt;/p&gt;
&lt;p&gt;The problem with jq is that it crashes on 400s or 500s because the response body is empty in those cases. Error responses are inherent to the demo, and crashes are not the most desirable thing during a presentation.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;m presenting at the <a href="https://www.wpc.education">WPC 2025 Conference</a> on December 3rd in Milan. My session topic is Feature Flag Management and Dynamic Configurations in C#.</p>
<p>I will use a Web API as an example project, and since I&rsquo;ll be using curl live to query it, I&rsquo;ll need to pipe responses through <a href="https://jqlang.org">jq</a> to obtain nicely formatted JSON for the audience.</p>
<p>The problem with jq is that it crashes on 400s or 500s because the response body is empty in those cases. Error responses are inherent to the demo, and crashes are not the most desirable thing during a presentation.</p>
<p>I cooked up a quick bash function that enhances curl and jq. It is called cj (curl + jq) and prevents crashes on errors, displays HTTP status codes with color-coded output (green for success, red for errors), and prettifies JSON responses.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>As I fully expect someone in the audience to raise their hand and ask what the hell &ldquo;cj&rdquo; is, I&rsquo;m posting it for reference so I can point them here if needed (Hi there!).</p>
<p>The function looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cj() {
</span></span><span style="display:flex;"><span>    local response http_code body
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    response=<span style="font-weight:bold">$(</span>curl -s -w <span style="font-style:italic">&#34;\n%{http_code}&#34;</span> <span style="font-style:italic">&#34;</span>$@<span style="font-style:italic">&#34;</span><span style="font-weight:bold">)</span>
</span></span><span style="display:flex;"><span>    http_code=<span style="font-weight:bold;font-style:italic">${</span>response##*<span style="font-style:italic">$&#39;\n&#39;</span><span style="font-weight:bold;font-style:italic">}</span>
</span></span><span style="display:flex;"><span>    body=<span style="font-weight:bold;font-style:italic">${</span>response%<span style="font-style:italic">$&#39;\n&#39;</span>*<span style="font-weight:bold;font-style:italic">}</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="font-weight:bold">if</span> [[ $http_code =~ ^2[0-9][0-9]$ ]]; <span style="font-weight:bold">then</span>
</span></span><span style="display:flex;"><span>        echo -e <span style="font-style:italic">&#34;\033[0;32mHTTP Code: </span>$http_code<span style="font-style:italic">\033[0m&#34;</span>
</span></span><span style="display:flex;"><span>        echo <span style="font-style:italic">&#34;</span>$body<span style="font-style:italic">&#34;</span> | jq 2&gt;/dev/null || echo <span style="font-style:italic">&#34;</span>$body<span style="font-style:italic">&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="font-weight:bold">else</span>
</span></span><span style="display:flex;"><span>        echo -e <span style="font-style:italic">&#34;\033[0;31mHTTP Code: </span>$http_code<span style="font-style:italic">\033[0m&#34;</span>
</span></span><span style="display:flex;"><span>        echo -e <span style="font-style:italic">&#34;\033[0;31m</span>$body<span style="font-style:italic">\033[0m&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="font-weight:bold">fi</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>It currently sits at the bottom of my .zshrc file. I might turn it into a script in the future, but it&rsquo;s probably going to be short-lived, so I&rsquo;m happy with its current residence.</p>
<p>It&rsquo;s pretty straightforward, but let&rsquo;s break it down line by line.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cj() {
</span></span></code></pre></div><p>Defines a function named cj (short for &ldquo;curl with jq&rdquo;), which will wrap the standard curl command with automatic JSON formatting and colored output.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>local response http_code body
</span></span></code></pre></div><p>Declares three local variables scoped to this function: <code>response</code> will store the full curl output, <code>http_code</code> will contain the HTTP status code, and <code>body</code> will hold the response body.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>response=<span style="font-weight:bold">$(</span>curl -s -w <span style="font-style:italic">&#34;\n%{http_code}&#34;</span> <span style="font-style:italic">&#34;</span>$@<span style="font-style:italic">&#34;</span><span style="font-weight:bold">)</span>
</span></span></code></pre></div><p>Executes curl with the <code>-s</code> flag for silent mode (no progress bar), <code>-w &quot;\n%{http_code}&quot;</code> to append a newline and the HTTP status code at the end of the output, and <code>&quot;$@&quot;</code> to forward all arguments passed to the function. The entire output is captured in the response variable.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>http_code=<span style="font-weight:bold;font-style:italic">${</span>response##*<span style="font-style:italic">$&#39;\n&#39;</span><span style="font-weight:bold;font-style:italic">}</span>
</span></span></code></pre></div><p>Extracts the HTTP status code using Bash parameter expansion. The <code>##*$'\n'</code> pattern removes everything up to and including the last newline, leaving only the status code. This is faster than using external commands like tail.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>body=<span style="font-weight:bold;font-style:italic">${</span>response%<span style="font-style:italic">$&#39;\n&#39;</span>*<span style="font-weight:bold;font-style:italic">}</span>
</span></span></code></pre></div><p>Extracts the response body using parameter expansion. The <code>%$'\n'*</code> pattern removes the last newline and everything after it (the status code), leaving only the body content. This is more efficient than using, say, sed.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="font-weight:bold">if</span> [[ $http_code =~ ^2[0-9][0-9]$ ]]; <span style="font-weight:bold">then</span>
</span></span></code></pre></div><p>Checks if the HTTP status code matches the pattern for success responses (2xx). The regex <code>^2[0-9][0-9]$</code> matches any three-digit number starting with 2.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>echo -e <span style="font-style:italic">&#34;\033[0;32mHTTP Code: </span>$http_code<span style="font-style:italic">\033[0m&#34;</span>
</span></span></code></pre></div><p>Prints the HTTP status code in green color. The <code>-e</code> flag enables interpretation of backslash escapes, <code>\033[0;32m</code> is the ANSI code for green text, and <code>\033[0m</code> resets the color back to default.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>echo <span style="font-style:italic">&#34;</span>$body<span style="font-style:italic">&#34;</span> | jq 2&gt;/dev/null || echo <span style="font-style:italic">&#34;</span>$body<span style="font-style:italic">&#34;</span>
</span></span></code></pre></div><p>This one was fun. Attempts to format the response body as JSON using jq. If jq is not installed or the body isn&rsquo;t valid JSON, stderr is redirected to <code>/dev/null</code> and the <code>||</code> operator triggers the fallback, which simply prints the raw body.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="font-weight:bold">else</span>
</span></span><span style="display:flex;"><span>        echo -e <span style="font-style:italic">&#34;\033[0;31mHTTP Code: </span>$http_code<span style="font-style:italic">\033[0m&#34;</span>
</span></span></code></pre></div><p>For non-2xx responses (errors), prints the HTTP status code in red color using the ANSI code <code>\033[0;31m</code>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>echo -e <span style="font-style:italic">&#34;\033[0;31m</span>$body<span style="font-style:italic">\033[0m&#34;</span>
</span></span></code></pre></div><p>Prints the error response body also in red color, making errors immediately visible during the demo.</p>
<p>And that is all.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I could of course update the API to return valid JSON even on errors, but that&rsquo;s boring.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>My most used command-line commands</title>
      <link>https://nicolaiarocci.com/my-most-used-command-line-commands/</link>
      <pubDate>Wed, 01 Jan 2025 12:33:34 +0100</pubDate>
      <guid>https://nicolaiarocci.com/my-most-used-command-line-commands/</guid>
      <description>&lt;p&gt;My most used command-line commands:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;5180 &lt;code&gt;git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;777 &lt;code&gt;cd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;653 &lt;code&gt;ls&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;452 &lt;code&gt;go&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;440 &lt;code&gt;./invoice&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;377 &lt;code&gt;dotnet&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;373 &lt;code&gt;rm&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;270 &lt;code&gt;vi&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;225 &lt;code&gt;cat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;219 &lt;code&gt;ssh&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Version control dominates the scene (a gentle middle finger for the youngster - you know who you are - who told me I should not be coding anymore). I also like that the list hints at the new stuff I&amp;rsquo;ve been working on recently and am excited about.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>My most used command-line commands:</p>
<ol>
<li>5180 <code>git</code></li>
<li>777 <code>cd</code></li>
<li>653 <code>ls</code></li>
<li>452 <code>go</code></li>
<li>440 <code>./invoice</code></li>
<li>377 <code>dotnet</code></li>
<li>373 <code>rm</code></li>
<li>270 <code>vi</code></li>
<li>225 <code>cat</code></li>
<li>219 <code>ssh</code></li>
</ol>
<p>Version control dominates the scene (a gentle middle finger for the youngster - you know who you are - who told me I should not be coding anymore). I also like that the list hints at the new stuff I&rsquo;ve been working on recently and am excited about.</p>
<p>Generated with <code>history | awk '{print $2}' | sort | uniq --count | sort --numeric-sort --reverse | head -10</code> and inspired by <a href="https://www.chrisdeluca.me/2024/12/31/my-cli-wrapped-most-used.html">Chris De Luca</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>ShellCheck</title>
      <link>https://nicolaiarocci.com/shellcheck/</link>
      <pubDate>Wed, 24 Jul 2024 14:59:21 +0200</pubDate>
      <guid>https://nicolaiarocci.com/shellcheck/</guid>
      <description>&lt;p&gt;Today I &lt;a href=&#34;https://www.simplermachines.com/how-to-write-better-bash-than-chatgpt/&#34;&gt;learned&lt;/a&gt; about &lt;a href=&#34;https://github.com/koalaman/shellcheck&#34;&gt;ShellCheck&lt;/a&gt;, a static analysis tool that &amp;ldquo;finds bugs in your scripts&amp;rdquo;. It can and should be run on the command line, but an &lt;a href=&#34;https://www.shellcheck.net&#34;&gt;online version&lt;/a&gt; is also available. It catches most style and syntax errors and has plenty of options, like ignoring specific errors and warnings, which is helpful in CI scenarios.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Today I <a href="https://www.simplermachines.com/how-to-write-better-bash-than-chatgpt/">learned</a> about <a href="https://github.com/koalaman/shellcheck">ShellCheck</a>, a static analysis tool that &ldquo;finds bugs in your scripts&rdquo;. It can and should be run on the command line, but an <a href="https://www.shellcheck.net">online version</a> is also available. It catches most style and syntax errors and has plenty of options, like ignoring specific errors and warnings, which is helpful in CI scenarios.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Bash-Oneliner: a collection of terminal tricks for Linux</title>
      <link>https://nicolaiarocci.com/bash-oneliner-a-collection-of-terminal-tricks-for-linux/</link>
      <pubDate>Mon, 22 Jul 2024 15:51:51 +0200</pubDate>
      <guid>https://nicolaiarocci.com/bash-oneliner-a-collection-of-terminal-tricks-for-linux/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/onceupon/Bash-Oneliner&#34;&gt;Bash-Oneliner&lt;/a&gt; is an excellent resource for Bash/Linux users. Most of the &amp;ldquo;tricks&amp;rdquo; are well-known, but there is always something to learn. More importantly, finding them all well organized in one file is rare.&lt;/p&gt;
&lt;p&gt;I use the reverse lookup of bash-history (Ctrl+R) daily. Still, only today (thanks to an HN &lt;a href=&#34;https://news.ycombinator.com/item?id=41033120&#34;&gt;comment&lt;/a&gt; on Bash-Onliner) did I learn that it also preserves one&amp;rsquo;s comments, which can be exploited to invoke complex commands quickly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ mv -n ~/Desktop/*.pdf ~/Documents/PDF_Archive/  &lt;span style=&#34;font-style:italic&#34;&gt;#pdfsync&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, you simply Ctrl-R and type &amp;ldquo;pdfsync&amp;rdquo; to recall the above command when needed. Neat.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://github.com/onceupon/Bash-Oneliner">Bash-Oneliner</a> is an excellent resource for Bash/Linux users. Most of the &ldquo;tricks&rdquo; are well-known, but there is always something to learn. More importantly, finding them all well organized in one file is rare.</p>
<p>I use the reverse lookup of bash-history (Ctrl+R) daily. Still, only today (thanks to an HN <a href="https://news.ycombinator.com/item?id=41033120">comment</a> on Bash-Onliner) did I learn that it also preserves one&rsquo;s comments, which can be exploited to invoke complex commands quickly:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ mv -n ~/Desktop/*.pdf ~/Documents/PDF_Archive/  <span style="font-style:italic">#pdfsync</span>
</span></span></code></pre></div><p>Then, you simply Ctrl-R and type &ldquo;pdfsync&rdquo; to recall the above command when needed. Neat.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to fix the crontab error `rename: Operation not permitted`</title>
      <link>https://nicolaiarocci.com/how-to-fix-the-crontab-error-rename-operation-not-permitted/</link>
      <pubDate>Thu, 25 Jan 2024 14:37:42 +0100</pubDate>
      <guid>https://nicolaiarocci.com/how-to-fix-the-crontab-error-rename-operation-not-permitted/</guid>
      <description>&lt;p&gt;Today, something unexpected happened while I was working on one of our Linux
machines. I issued the &lt;code&gt;crontab -e&lt;/code&gt; command to add a cron job to my user&amp;rsquo;s
crontab file, modified and saved it, only to the following error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;crontab: installing new crontab
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;crontab: crontabs/&amp;lt;user&amp;gt;: rename: Operation not permitted
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;crontab: edits left in /tmp/crontab.hgmsOH/crontab
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Puzzled, I checked whether my user permissions were all right, if the disk was
full, and several other things. Long story short, the fix was this one:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Today, something unexpected happened while I was working on one of our Linux
machines. I issued the <code>crontab -e</code> command to add a cron job to my user&rsquo;s
crontab file, modified and saved it, only to the following error:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>crontab: installing new crontab
</span></span><span style="display:flex;"><span>crontab: crontabs/&lt;user&gt;: rename: Operation not permitted
</span></span><span style="display:flex;"><span>crontab: edits left in /tmp/crontab.hgmsOH/crontab
</span></span></code></pre></div><p>Puzzled, I checked whether my user permissions were all right, if the disk was
full, and several other things. Long story short, the fix was this one:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo chattr -i /var/spool/cron/crontabs/&lt;user&gt;
</span></span><span style="display:flex;"><span>sudo chown &lt;user&gt;:crontab /var/spool/cron/crontabs/&lt;user&gt;
</span></span></code></pre></div><p>Somehow, the user&rsquo;s crontab file had become immutable.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to use bash to recursively search and replace a string in all directory files</title>
      <link>https://nicolaiarocci.com/how-to-use-bash-to-recursively-search-and-replace-a-string-in-all-directory-files/</link>
      <pubDate>Sat, 06 Jan 2024 09:34:16 +0100</pubDate>
      <guid>https://nicolaiarocci.com/how-to-use-bash-to-recursively-search-and-replace-a-string-in-all-directory-files/</guid>
      <description>&lt;p&gt;Another achievement I unlocked with the &lt;a href=&#34;https://nicolaiarocci.com/new-website-finally-with-no-analytics/&#34;&gt;recent website
update&lt;/a&gt; is the newsletter switch from Substack to
a fantastic and independent provider,
&lt;a href=&#34;https://buttondown.email/refer/nicolaiarocci&#34;&gt;Buttondown&lt;/a&gt;. That required
updating all the &amp;ldquo;subscribe to my newsletter&amp;rdquo; links. We&amp;rsquo;re talking 5K posts, all
saved as individual files in the same directory. The bash command that did that
for me is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find content/post/*.md -type f -exec &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;&lt;/span&gt;    sed -i .bak &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#39;s|https://nicolaiarocci.substack.com|https://buttondown.email/nicolaiarocci|g&amp;#39;&lt;/span&gt; {} +
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It is pretty straightforward. &lt;code&gt;find&lt;/code&gt; looks for all markdown files in the
&lt;code&gt;content/post/&lt;/code&gt; directory. On each file, &lt;code&gt;sed&lt;/code&gt; performs a search-and-replace
action. Notice that I use &lt;code&gt;|&lt;/code&gt; instead of the standard &lt;code&gt;/&lt;/code&gt; as a separator for the
search-and-replace pattern , and that&amp;rsquo;s because the pattern itself has &lt;code&gt;/&lt;/code&gt;s in
the URLs so I need to differentiate. Also, on macOS, the &lt;code&gt;-i&lt;/code&gt; parameter requires
a backup file argument (&amp;quot;*.bak&amp;quot;) to make a backup copy before the update. This
argument is unnecessary in newer sed versions and will perform an in-place
update if not provided.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Another achievement I unlocked with the <a href="/new-website-finally-with-no-analytics/">recent website
update</a> is the newsletter switch from Substack to
a fantastic and independent provider,
<a href="https://buttondown.email/refer/nicolaiarocci">Buttondown</a>. That required
updating all the &ldquo;subscribe to my newsletter&rdquo; links. We&rsquo;re talking 5K posts, all
saved as individual files in the same directory. The bash command that did that
for me is:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find content/post/*.md -type f -exec <span style="font-weight:bold;font-style:italic">\
</span></span></span><span style="display:flex;"><span><span style="font-weight:bold;font-style:italic"></span>    sed -i .bak <span style="font-style:italic">&#39;s|https://nicolaiarocci.substack.com|https://buttondown.email/nicolaiarocci|g&#39;</span> {} +
</span></span></code></pre></div><p>It is pretty straightforward. <code>find</code> looks for all markdown files in the
<code>content/post/</code> directory. On each file, <code>sed</code> performs a search-and-replace
action. Notice that I use <code>|</code> instead of the standard <code>/</code> as a separator for the
search-and-replace pattern , and that&rsquo;s because the pattern itself has <code>/</code>s in
the URLs so I need to differentiate. Also, on macOS, the <code>-i</code> parameter requires
a backup file argument (&quot;*.bak&quot;) to make a backup copy before the update. This
argument is unnecessary in newer sed versions and will perform an in-place
update if not provided.</p>
<p>Later, I realized I would be better off if I removed the call to action from my
posts and added it to the footer template instead. That way, I&rsquo;d only have one
place to edit or update it in the future, and my RSS feed (and newsletter
updates,  as they draw from the RSS feed) would be clean of unnecessary spam. In
hindsight, this a move I could&rsquo;ve made many years ago (2010?) when I installed
Hugo for the first time, but hey, better late than never. But how could I delete
calls to action from all my 5K posts? With a variation of the above command, of
course:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find content/post/*.md -type f -exec sed -i .bak <span style="">&#39;</span>/
</span></span></code></pre></div><p>It is the same logic as above, but we&rsquo;re replacing the matched string with a
&ldquo;delete all lines till the end of the file&rdquo; pattern this time. I must admit that
this one was trickier to pull off and required a discrete amount of trial and
error.</p>
]]></content:encoded>
    </item>
    <item>
      <title>rsync with a different user</title>
      <link>https://nicolaiarocci.com/rsync-with-a-different-user/</link>
      <pubDate>Wed, 23 Aug 2023 07:05:25 +0100</pubDate>
      <guid>https://nicolaiarocci.com/rsync-with-a-different-user/</guid>
      <description>&lt;p&gt;Today I learned how to rsync with a user different than the one connected to the remote. Why would one want to do such a
thing? The data I need to download from that server is owned by &amp;lsquo;backup,&amp;rsquo; a different, service-only user. I wanted to
avoid going the change-permissions slippery route and allow my user direct access to the data.&lt;/p&gt;
&lt;p&gt;Looking at the rsync documentation, I learned about the nifty &lt;code&gt;--rsync-path=PROGRAM&lt;/code&gt; &lt;a href=&#34;https://download.samba.org/pub/rsync/rsync.1#opt--rsync-path&#34;&gt;option&lt;/a&gt;:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Today I learned how to rsync with a user different than the one connected to the remote. Why would one want to do such a
thing? The data I need to download from that server is owned by &lsquo;backup,&rsquo; a different, service-only user. I wanted to
avoid going the change-permissions slippery route and allow my user direct access to the data.</p>
<p>Looking at the rsync documentation, I learned about the nifty <code>--rsync-path=PROGRAM</code> <a href="https://download.samba.org/pub/rsync/rsync.1#opt--rsync-path">option</a>:</p>
<blockquote>
<p>Use this to specify what program is to be run on the remote machine to start-up rsync. Often used when rsync is not in
the default remote-shell&rsquo;s path (e.g. &ndash;rsync-path=/usr/local/bin/rsync).</p></blockquote>
<p>The example caught my attention. I could perhaps leverage this option to perform a user switch before executing rsync
(now performed on the remote machine). As further research confirmed, it <a href="https://unix.stackexchange.com/a/546296">can be done</a>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>rsync --rsync-path &#39;sudo -u backup rsync&#39; -a --delete host:source destination
</span></span></code></pre></div><p>It didn&rsquo;t work immediately because my user was not a sudoer, but that was an easy fix:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>cat &gt; /etc/sudoers.d/myuser &lt;&lt; EOF
</span></span><span style="display:flex;"><span>myuser ALL=(ALL) NOPASSWD:/usr/bin/rsync
</span></span><span style="display:flex;"><span>EOF
</span></span></code></pre></div><p>As you can see, not only must the user be a sudoer, but it also needs to be able to sudo with no password. One last
minor issue was that &lsquo;backup&rsquo;, being a service user, had no shell access. That was another easy fix:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>sudo usermod -s /bin/bash backup
</span></span></code></pre></div><p>Now my rsync-with-a-different-user command works like a charm. I do have some mild security concerns, though. My user is
a sudoer (can only sudo rsync, though<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>), and &lsquo;backup&rsquo; now has shell access. As password logins are turned off, and
SSH keys-only access is allowed to the machine (and I&rsquo;m the only person holding those keys), everything&rsquo;s still
reasonably safe. Are there better ways? Please let me know.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Thanks to Sebastian on Mastodon for <a href="https://fosstodon.org/@DarkMetatron@rollenspiel.social/110939139075153024">pointing out</a> that I should limit the sudoer to just rsync itself. I updated the post accordingly. By the way, this is why I love posting TILs in public.
[rss]: <a href="https://nicolaiarocci.com/index.xml">https://nicolaiarocci.com/index.xml</a>
[m]: <a href="https://fosstodon.org/@nicola">https://fosstodon.org/@nicola</a>
[nl]: <a href="https://buttondown.email/nicolaiarocci">https://buttondown.email/nicolaiarocci</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
  </channel>
</rss>
