<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0"
  xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
  xmlns:atom="http://www.w3.org/2005/Atom">

<channel>

<title>Blog — George Mishurovsky: posts tagged shell</title>
<link>https://mishurovsky.com/blog/?go=tags/shell/</link>
<description>A blog by George Mishurovsky — a senior software engineer with a medical degree. Drawing from both engineering and scientific thinking, he explores software, architecture, design, psychology, and product thinking.</description>
<author></author>
<language>en</language>
<generator>Aegea 11.3 (v4134e)</generator>

<itunes:owner>
<itunes:name></itunes:name>
<itunes:email>george@mishurovsky.com</itunes:email>
</itunes:owner>
<itunes:subtitle>A blog by George Mishurovsky — a senior software engineer with a medical degree. Drawing from both engineering and scientific thinking, he explores software, architecture, design, psychology, and product thinking.</itunes:subtitle>
<itunes:image href="https://mishurovsky.com/blog/pictures/userpic/userpic-square@2x.jpg?1753619610" />
<itunes:explicit>no</itunes:explicit>

<item>
<title>&lt;span class="inline-code"&gt;jq&lt;/span&gt; is Wonderful!</title>
<guid isPermaLink="false">50</guid>
<link>https://mishurovsky.com/blog/?go=all/jq-is-wonderful/</link>
<pubDate>Mon, 30 Mar 2026 11:05:29 +0200</pubDate>
<author></author>
<comments>https://mishurovsky.com/blog/?go=all/jq-is-wonderful/</comments>
<description>
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://mishurovsky.com/blog/pictures/jq-is-wonderful@2x.jpg" width="924" height="327" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Linux is a brilliant OS — because it has a great set of tools to work with text files! Today I’ve tried to use &lt;span class="inline-code"&gt;jq&lt;/span&gt; to come up with a little wonder I find worth sharing. Might be it will make your life simpler too.&lt;/p&gt;
&lt;p&gt;Suppose you have a &lt;span class="inline-code"&gt;chat-export.json&lt;/span&gt; with your monthly spendings mixed with other messages like this:&lt;/p&gt;
&lt;div class="e2-code-block" data-language="json"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;JSON&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-json"&gt;[
  {
    &amp;quot;id&amp;quot;: 139,
    &amp;quot;message&amp;quot;: &amp;quot;18 pizza in Pizza World&amp;quot;,
    &amp;quot;chat&amp;quot;: {
      &amp;quot;id&amp;quot;: 123,
    },
    &amp;quot;date&amp;quot;: &amp;quot;2026-02-26 10:15:00+00:00&amp;quot;
    // ...30 fields more
  },
  {
    &amp;quot;id&amp;quot;: 140,
    &amp;quot;message&amp;quot;: &amp;quot;120 bills&amp;quot;,
    &amp;quot;chat&amp;quot;: {
      &amp;quot;id&amp;quot;: 123,
    },
    &amp;quot;date&amp;quot;: &amp;quot;2026-03-02 14:30:00+00:00&amp;quot;
  },
  {
    &amp;quot;id&amp;quot;: 141,
    &amp;quot;message&amp;quot;: &amp;quot;Some message from another chat&amp;quot;,
    &amp;quot;chat&amp;quot;: {
      &amp;quot;id&amp;quot;: 140,
    },
    &amp;quot;date&amp;quot;: &amp;quot;2026-03-02 19:20:00+00:00&amp;quot;
  },
  {
    &amp;quot;id&amp;quot;: 142,
    &amp;quot;message&amp;quot;: &amp;quot;3.5 coffee in Cream&amp;quot;,
    &amp;quot;chat&amp;quot;: {
      &amp;quot;id&amp;quot;: 123,
    },
    &amp;quot;date&amp;quot;: &amp;quot;2026-03-08 08:45:00+00:00&amp;quot;
  },
  {
    &amp;quot;id&amp;quot;: 143,
    &amp;quot;message&amp;quot;: &amp;quot;Another unrelated message&amp;quot;,
    &amp;quot;chat&amp;quot;: {
      &amp;quot;id&amp;quot;: 156,
    },
    &amp;quot;date&amp;quot;: &amp;quot;2026-03-11 12:05:00+00:00&amp;quot;
  },
  // ...1000 entries more
]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;How can you calculate your spendings in March *fast*? &lt;span class="inline-code"&gt;jq&lt;/span&gt; to the rescue!&lt;/p&gt;
&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;jq &amp;#039;[ .[]                       # iterate over array items
  | select(.chat.id == 123)     # filter the ones from budget chat
  | select(.date | startswith(&amp;quot;2026-03&amp;quot;)) # filter the ones from March
  | .message                    # extract message text
  | gsub(&amp;quot;[^0-9.]&amp;quot;; &amp;quot;&amp;quot;)         # leave only numeric symbols and dots
  | tonumber                    # convert text to numbers
] | add&amp;#039; chat-export.json       # get the sum!&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or as a one-liner:&lt;/p&gt;
&lt;div class="e2-code-block" data-language="shell" data-long&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-shell"&gt;jq &amp;#039;[ .[] | select(.chat.id == 123) | select(.date | startswith(&amp;quot;2026-03&amp;quot;)) | .message | gsub(&amp;quot;[^0-9.]&amp;quot;; &amp;quot;&amp;quot;) | tonumber ] | add&amp;#039; chat-export.json&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As short as it can get, clear, easy to use — and no software involved apart from utilities that come with the system. Shiny! ✨&lt;/p&gt;
</description>
</item>

<item>
<title>Renaming Entities Project-Wide with find, grep, sed, and rename</title>
<guid isPermaLink="false">17</guid>
<link>https://mishurovsky.com/blog/?go=all/how-to-bulk-rename-some-entity-and-files-in-a-project/</link>
<pubDate>Mon, 20 Oct 2025 15:07:33 +0200</pubDate>
<author></author>
<comments>https://mishurovsky.com/blog/?go=all/how-to-bulk-rename-some-entity-and-files-in-a-project/</comments>
<description>
&lt;p&gt;There are times in software projects when a big shift happens in domain representation. This results in changes of project structure, class responsibilities, and occasionally, requires bulk renames of entities across the whole codebase.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://mishurovsky.com/blog/pictures/bulk-rename@2x.png" width="667" height="388" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Imagine we need to rename every &lt;span class="inline-code"&gt;Employee&lt;/span&gt; to &lt;span class="inline-code"&gt;Worker&lt;/span&gt;. This change should affect both file paths and textual occurrences throughout the project.&lt;/p&gt;
&lt;p&gt;Renaming may sound like a simple problem: assuming there are no external dependencies using the target name, we just need to rename all occurrences of it inside a repository. But there multiple caveats:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;We must rename both file names and folder names.&lt;/li&gt;
&lt;li&gt;We must rename all code and text occurrences.&lt;/li&gt;
&lt;li&gt;Casing must be preserved: &lt;span class="inline-code"&gt;Employee → Worker&lt;/span&gt;, and &lt;span class="inline-code"&gt;employee → worker&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Both plain and compound usages must be properly renamed: &lt;span class="inline-code"&gt;createEmployee → createWorker&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Non-code,  non-document files must not be affected (consider binary files which by coincidence might have an &lt;span class="inline-code"&gt;...employee...&lt;/span&gt; fragment inside).&lt;/li&gt;
&lt;li&gt;There are folders or files which we would want to omit from renaming (e. g., &lt;span class="inline-code"&gt;.git&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;No IDE provides such functionality, so we cannot rely on existing solutions.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I will address all these challenges in a solution below, but there is an important complexity that cannot be tackled with automation. If by any chance your code depends on a library with the target name (e. g., &lt;span class="inline-code"&gt;employee.js&lt;/span&gt;) or uses exports from a library containing the target name (&lt;span class="inline-code"&gt;import type { valuableEmployeee } from ‘employee-js’&lt;/span&gt;), you’ll have to resolve issues manually after renaming.&lt;/p&gt;
&lt;h2&gt;Reviewing Expected Changes&lt;/h2&gt;
&lt;p&gt;First, remove any folders and files that are recreated during project builds or setup: &lt;span class="inline-code"&gt;build/&lt;/span&gt;, &lt;span class="inline-code"&gt;dist/&lt;/span&gt;, &lt;span class="inline-code"&gt;node_modules/&lt;/span&gt;, &lt;span class="inline-code"&gt;.storybook-static/&lt;/span&gt;, etc. This step isn’t strictly necessary, but it can help iterate faster if commands encounter errors.&lt;/p&gt;
&lt;p&gt;Now, let’s start with renaming text occurrences by listing all files that might get affected with &lt;span class="inline-code"&gt;find&lt;/span&gt; command. Here I am using &lt;span class="inline-code"&gt;-iname&lt;/span&gt; for case-insensitive search and `-not -path ‘&lt;name&gt;’` syntax to exclude folders we need to protect from changes.&lt;/p&gt;
&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;find . -not -path &amp;#039;*/.*&amp;#039; -not -path &amp;#039;src/protected&amp;#039; -iname &amp;#039;*employee*&amp;#039;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Revise the output: make sure it does not contain folders or files you do not want to be changed.&lt;/p&gt;
&lt;p&gt;Then, let’s see which text occurrences inside our files will be affected. Same approach: case-insensitive search in all files, excluding protected directories or files.&lt;/p&gt;
&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;grep -RIn -i --exclude-dir=&amp;#039;*/.*&amp;#039; --exclude-dir=&amp;#039;src/protected&amp;#039; &amp;#039;employee&amp;#039; .&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the output you’ll see all lines containing the target name. Revise all them carefully: if you want to protect some names from changes, you might want to add file names to exclusion, or rename such occurrences manually to some special value (e. g. &lt;span class="inline-code"&gt;em#plo#yee&lt;/span&gt;), so you can revert it later.&lt;/p&gt;
&lt;h2&gt;Renaming Text Occurences&lt;/h2&gt;
&lt;p&gt;Now we can rename all text occurrences, handling separately each casing. It will require some &lt;span class="inline-code"&gt;sed&lt;/span&gt; magic:&lt;/p&gt;
&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;LC_CTYPE=UTF-8 find . -type f \
  -not -path &amp;#039;*/.*&amp;#039; \
  \( -name &amp;#039;*.ts&amp;#039; -o -name &amp;#039;*.tsx&amp;#039; -o -name &amp;#039;*.js&amp;#039; -o -name &amp;#039;*.json&amp;#039; \) \
  -exec sed -i &amp;#039;s/Employee/Worker/g; s/employee/worker/g&amp;#039; {} +&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There are two important points here. First, &lt;span class="inline-code"&gt;LC_CTYPE=UTF-8&lt;/span&gt; allows us to treat all file characters as UTF-8, even if the situation is different. Without it, &lt;span class="inline-code"&gt;sed&lt;/span&gt; stops when encounters non-UTF-8 characters. Second, we use &lt;span class="inline-code"&gt;-o -name ‘*.ext’&lt;/span&gt; syntax to list file extensions to be affected. This prevents accidental changes of binary or image file contents.&lt;/p&gt;
&lt;h2&gt;Renaming Paths&lt;/h2&gt;
&lt;p&gt;Hopefully, file content renaming finished successfully. From here we will proceed with renaming of file and folder names. For this we will use &lt;span class="inline-code"&gt;rename&lt;/span&gt; command ingesting &lt;span class="inline-code"&gt;find&lt;/span&gt; output:&lt;/p&gt;
&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;find . -not -path &amp;#039;*/.*&amp;#039; -depth -name &amp;#039;*Employee*&amp;#039; \
  -exec rename &amp;#039;s/Employee/Worker/g&amp;#039; {} +
find . -not -path &amp;#039;*/.*&amp;#039; -depth -name &amp;#039;*employee*&amp;#039; \
  -exec rename &amp;#039;s/employee/worker/g&amp;#039; {} +&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These commands might produce warnings. If you have file paths that include multiple occurrences of the target name, the renames will be performed only for the first one, so you will have to run the commands multiple times until the paths are fully renamed.&lt;/p&gt;
&lt;p&gt;And that’s it! ✨&lt;br /&gt;
If you created any specially-renamed entities, rename them back manually. Then run &lt;span class="inline-code"&gt;git add&lt;/span&gt; and &lt;span class="inline-code"&gt;git commit&lt;/span&gt; — git should detect all path renames automatically.&lt;/p&gt;
</description>
</item>

<item>
<title>How to Delete All Local Git Branches in One Command</title>
<guid isPermaLink="false">11</guid>
<link>https://mishurovsky.com/blog/?go=all/how-to-delete-all-local-git-branches/</link>
<pubDate>Thu, 28 Aug 2025 13:46:06 +0200</pubDate>
<author></author>
<comments>https://mishurovsky.com/blog/?go=all/how-to-delete-all-local-git-branches/</comments>
<description>
&lt;p&gt;First, checkout to main (or any other branch you want to clear from upstream branches).&lt;/p&gt;
&lt;p&gt;Now, let’s build the command, step by step.&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Check which branches were merged to the current branch:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;git branch --merged&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="2"&gt;
&lt;li&gt;Filter out current branch from the output – it is marked by an asterisk (*):&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;git branch --merged | grep -v \*&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="3"&gt;
&lt;li&gt;Turn the columnar output into a space-separated string:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;git branch --merged | grep -v \* | xargs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="4"&gt;
&lt;li&gt;Feed these arguments to the deletion command (passed in the second argument of &lt;span class="inline-code"&gt;xargs&lt;/span&gt;):&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;git branch --merged | grep -v \* | xargs git branch -D&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
</item>

<item>
<title>How to Recall Your Google Meets Fast</title>
<guid isPermaLink="false">8</guid>
<link>https://mishurovsky.com/blog/?go=all/how-to-recall-your-google-meets-fast/</link>
<pubDate>Sat, 16 Aug 2025 15:25:27 +0200</pubDate>
<author></author>
<comments>https://mishurovsky.com/blog/?go=all/how-to-recall-your-google-meets-fast/</comments>
<description>
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://mishurovsky.com/blog/pictures/google-takeout-meetings@2x.png" width="543" height="293" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;I have to confess. I am a sinner — I constantly forget to log my time spent on tasks! I postpone this demanding chore for a week, until our PM comes bashing my door (and I work remotely!): “Please log your time, we need to create reports!” And here lies the problem: usually, I can hardly remember even what happened yesterday, let alone the whole week. I believe I am not alone here.&lt;/p&gt;
&lt;p&gt;Alright, so now I need to log my work time for the week. I can track code contributions by commit dates, but how do I log meeting times? A common way to do this is to sweep through emails, Slack messages and meeting notes, but it is chaotic and time-consuming. If you use Google Meet, there is a much more straightforward way — Google Takeout!&lt;/p&gt;
&lt;p&gt;Google Takeout is a service that allows you to download all data Google keeps about your account. This is a very interesting yet terrifying resource: you’ll find data from over 60 different services, some of which may keep gigabytes of your data! But for our current goal, we only need Google Meet data.&lt;/p&gt;
&lt;h2&gt;What to do&lt;/h2&gt;
&lt;p&gt;First, visit &lt;a href="https://takeout.google.com/"&gt;https://takeout.google.com/&lt;/a&gt; from a work account. Deselect all checkboxes, then find and mark Google Meet. Scroll to the bottom of the page, and click primary-colored buttons a couple of times. Google will prepare data export and will send a link to your email. Use it to download a zip archive with data and unpack it.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://mishurovsky.com/blog/pictures/google-takeout-meetings-step-1-2@2x.jpg" width="1280" height="790" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;When you get to Google Takeout page, deselect all checkboxes and then find and mark Google Meet.&lt;/div&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://mishurovsky.com/blog/pictures/google-takeout-meetings-step-3-4@2x.jpg" width="1280" height="790" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;Click “Next step”, then “Create export” — Google will send a report to your account email in a minute.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The downloaded folder has a nested structure of &lt;span class="inline-code"&gt;./Takeout/Google Meet/ConferenceHistory&lt;/span&gt; with two &lt;span class="inline-code"&gt;.csv&lt;/span&gt; files inside. We will need only &lt;span class="inline-code"&gt;conference_history_records.csv&lt;/span&gt;. It is a large csv file with about 20 columns, holding information about all meets for your account. Let’s tidy it up with some command line magic to get a convenient output:&lt;/p&gt;
&lt;div class="e2-code-block" data-language="shell" data-long&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-shell"&gt;awk -F &amp;#039;,&amp;#039; &amp;#039;{print $5 &amp;quot;\t&amp;quot; $10 &amp;quot;\t&amp;quot; $12}&amp;#039; &amp;quot;~/Downloads/Takeout/Google Meet/ConferenceHistory/conference_history_records.csv&amp;quot; | head -n 20 | column -ts $&amp;#039;\t&amp;#039;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This command parses the csv and outputs only important data in a columnar view: meeting code (the same one used in Google Meet Links), date and time of the meet and its duration.&lt;/p&gt;
&lt;div class="e2-code-block"&gt;&lt;pre&gt;&lt;code class=""&gt;Meeting Code  Start Time               Duration
rst-uvwx-yza  2025-08-17 14:14:14 UTC  1:07:18
fgh-ijkl-mno  2025-08-16 06:36:12 UTC  0:23:57
vwx-yzab-cde  2025-08-15 17:20:33 UTC  0:41:03
klm-nopq-rst  2025-08-14 09:09:09 UTC  1:15:42
yza-bcde-fgh  2025-08-13 20:48:06 UTC  0:55:56
hij-klmn-opq  2025-08-12 07:02:08 UTC  0:17:15
opq-rstu-vwx  2025-08-11 15:33:54 UTC  0:34:29
tuv-wxyz-abc  2025-08-10 05:44:21 UTC  1:49:37
def-ghij-klm  2025-08-09 19:59:59 UTC  0:26:04
uvw-xyza-bcd  2025-08-08 11:11:11 UTC  0:09:48&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now it is much easier to recall which meetings they were and how long they lasted. Unfortunately, this file does not provide meeting names — but now retrieving them is easy: just copy-paste meeting code into your gmail search box, and you will find your invitation email with all the details.&lt;/p&gt;
&lt;p&gt;This way I manage to save myself some 15 to 20 minutes each week. I hope this trick helps you, too.&lt;/p&gt;
</description>
</item>

<item>
<title>Shell Configs for Better Command History Search</title>
<guid isPermaLink="false">6</guid>
<link>https://mishurovsky.com/blog/?go=all/shell-configs-for-better-command-history-search/</link>
<pubDate>Thu, 07 Aug 2025 12:54:33 +0200</pubDate>
<author></author>
<comments>https://mishurovsky.com/blog/?go=all/shell-configs-for-better-command-history-search/</comments>
<description>
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://mishurovsky.com/blog/pictures/better-shell-search@2x.jpg" width="766" height="381" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;In continuation of my previous post, &lt;a href="https://mishurovsky.com/blog/all/how-to-quickly-retrieve-past-terminal-commands/" class="nu"&gt;“&lt;u&gt;How to View Past Terminal Commands: from Simple to Robust&lt;/u&gt;”&lt;/a&gt;, I want to share shell config settings that help finding commands faster, while also looking for them much more further in the past.&lt;/p&gt;
&lt;h2&gt;Increase History Limits&lt;/h2&gt;
&lt;p&gt;First, &lt;span class="inline-code"&gt;HISTSIZE&lt;/span&gt; and &lt;span class="inline-code"&gt;HISTFILESIZE&lt;/span&gt;. These settings control how many past commands are stored in session memory and in the history file, respectively. Their defaults are 1000 commands for &lt;span class="inline-code"&gt;HISTSIZE&lt;/span&gt; and 2000 commands for &lt;span class="inline-code"&gt;HISTFILESIZE&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;This is way too low for modern computers. If an average command length is 20 characters, then history settings limit us to only 40 KB in RAM and 80 KB on disk. Also there is no real benefit to storing fewer commands in memory: if a history entry exists, you should be able to access it using &lt;span class="inline-code"&gt;history&lt;/span&gt; command without additional tricks.&lt;/p&gt;
&lt;p&gt;Let’s increase the limits:&lt;/p&gt;
&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;# bash
HISTSIZE=50000
HISTFILESIZE=50000

# zsh
HISTSIZE=50000
SAVEHIST=50000&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2&gt;Remove Duplicates in Search&lt;/h2&gt;
&lt;p&gt;Now, let’s compress history entries. When I lookup commands using reverse search (Ctrl+R), I do not want to see duplicates like &lt;span class="inline-code"&gt;docker build&lt;/span&gt;. Let’s keep only the most recent copy of each command:&lt;/p&gt;
&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;# bash
HISTCONTROL=ignoredups:erasedups

# zsh
setopt HIST_IGNORE_ALL_DUPS&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2&gt;Ignore Noise in History&lt;/h2&gt;
&lt;p&gt;When I use command search using arrow keys, I want to get quicker to useful commands, rather than wasting time and attention to common simple commands, such as &lt;span class="inline-code"&gt;ls&lt;/span&gt; or &lt;span class="inline-code"&gt;cd&lt;/span&gt;. Let’s prevent them from being saved at all:&lt;/p&gt;
&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;# bash
HISTIGNORE=&amp;quot;ls:cd:cd -:pwd:exit:clear&amp;quot;

# zsh
HIST_SKIP_PATTERN=&amp;#039;^(cd|ls|pwd|clear|exit)(\s|$)&amp;#039;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2&gt;Sync History Across Sessions&lt;/h2&gt;
&lt;p&gt;By default, history is saved only when a session closes. Let’s fix it: the terminal should append new commands and make them accessible in all open sessions immediately:&lt;/p&gt;
&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;# bash
PROMPT_COMMAND=&amp;#039;history -a; history -n&amp;#039;
shopt -s histappend

# zsh
setopt SHARE_HISTORY
setopt APPEND_HISTORY
setopt INC_APPEND_HISTORY&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2&gt;Final Setup&lt;/h2&gt;
&lt;p&gt;Now we are good! Below are full settings for both bash and zsh. Do not forget to run &lt;span class="inline-code"&gt;source ~/.bashrc&lt;/span&gt; or &lt;span class="inline-code"&gt;source ~/.zshrc&lt;/span&gt; after you make the changes.&lt;/p&gt;
&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;# bash
HISTSIZE=50000
HISTFILESIZE=50000

HISTCONTROL=ignoredups:erasedups
HISTIGNORE=&amp;quot;ls:cd:cd -:pwd:exit:clear&amp;quot;

PROMPT_COMMAND=&amp;#039;history -a; history -n; $PROMPT_COMMAND&amp;#039;
shopt -s histappend&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;# zsh
HISTSIZE=50000
SAVEHIST=50000

setopt HIST_IGNORE_ALL_DUPS
HIST_SKIP_PATTERN=&amp;#039;^(cd|ls|pwd|clear|exit)(\s|$)&amp;#039;

setopt APPEND_HISTORY
setopt SHARE_HISTORY
setopt INC_APPEND_HISTORY&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Happy command searching!&lt;/p&gt;
&lt;h2&gt;P.S. Want Full Command Logging?&lt;/h2&gt;
&lt;p&gt;If you want to keep full command history, you are not constrained to inefficient search. You still can apply all the changes above, but additionally configure the shell to store full log in a separate file:&lt;/p&gt;
&lt;div class="e2-code-block" data-language="sh"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-sh"&gt;# bash
export LOGFILE=~/.full_bash_history.log
PROMPT_COMMAND=&amp;#039;
  history -a
  history -n
  this_command=$(history 1 | sed &amp;quot;s/^[ ]*[0-9]*[ ]*//&amp;quot;)
  echo &amp;quot;$(date &amp;quot;+%Y-%m-%d %H:%M:%S&amp;quot;)  $this_command&amp;quot; &amp;gt;&amp;gt; &amp;quot;$LOGFILE&amp;quot;
&amp;#039;

# zsh
function preexec() {
  local LOGFILE=~/.full_zsh_history.log
  echo &amp;quot;$(date &amp;#039;+%Y-%m-%d %H:%M:%S&amp;#039;)  $1&amp;quot; &amp;gt;&amp;gt; &amp;quot;$LOGFILE&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
</item>

<item>
<title>How to View Past Terminal Commands — from Simple to Robust</title>
<guid isPermaLink="false">5</guid>
<link>https://mishurovsky.com/blog/?go=all/how-to-quickly-retrieve-past-terminal-commands/</link>
<pubDate>Sun, 03 Aug 2025 14:18:24 +0200</pubDate>
<author></author>
<comments>https://mishurovsky.com/blog/?go=all/how-to-quickly-retrieve-past-terminal-commands/</comments>
<description>
&lt;p&gt;Suppose you want to re-run some shell command you used ten days ago. It is a complex one; you do not remember exact flags and argument values, and it would take a long time to recall an exact text. What can you do?&lt;/p&gt;
&lt;h2&gt;1. The upwards arrow&lt;/h2&gt;
&lt;p&gt;Majority of devs working with command line knows it. Press “up” to see a previous command, press “down” for a next command, press “Ctrl+C” to drop whatever is in the prompt and start fresh.&lt;/p&gt;
&lt;p&gt;This approach works, but gets very tedious when you need to find a command you used last week or last month. Once more than ten or twenty commands have passed, scrolling through them becomes tedious.&lt;/p&gt;
&lt;h2&gt;2. Terminal history file&lt;/h2&gt;
&lt;p&gt;All the commands you enter into a terminal get stored in &lt;span class="inline-code"&gt;.bash_history&lt;/span&gt; file (or &lt;span class="inline-code"&gt;.zsh_history&lt;/span&gt; if you are on Mac) up to a certain limit. Thus, you can run:&lt;/p&gt;
&lt;div class="e2-code-block" data-language="shell"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-shell"&gt;cat ~/.bash_history # output all into the terminal
less ~/.bash_history # or use any text viewer
tail -n 20 ~/.bash_history # or observe only the most recent n lines
cat ~/.bash_history | grep whatever # to search for specific patterns&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This method gives you full access to your history file, and lets you search more flexibly.&lt;/p&gt;
&lt;h2&gt;3. &lt;span class="inline-code"&gt;history&lt;/span&gt; command&lt;/h2&gt;
&lt;p&gt;Almost the same as using the history file directly: you get a list commands, but now it is &lt;i&gt;numbered&lt;/i&gt;.&lt;/p&gt;
&lt;div class="e2-code-block" data-language="shell"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-shell"&gt;history -20 # show the last 20 commands
history -500 | grep ssh # search for a specific patter in a command
!780 # execute command with order number 780&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But there is one important difference from direct usage of &lt;span class="inline-code"&gt;.bash_history&lt;/span&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="inline-code"&gt;history&lt;/span&gt; command uses the last &lt;span class="inline-code"&gt;HISTSIZE&lt;/span&gt; history entries (default 1000)&lt;/li&gt;
&lt;li&gt;&lt;span class="inline-code"&gt;.bash_history&lt;/span&gt; file uses the last &lt;span class="inline-code"&gt;HISTFILESIZE&lt;/span&gt; entries (default 2000)&lt;br /&gt;
So, if your command was run a really long time ago, &lt;span class="inline-code"&gt;history&lt;/span&gt; may not find it, but direct inspection of &lt;span class="inline-code"&gt;.bash_history&lt;/span&gt; can do.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. &lt;span class="inline-code"&gt;fc -l&lt;/span&gt; command&lt;/h2&gt;
&lt;p&gt;This command behaves very similar to &lt;span class="inline-code"&gt;history&lt;/span&gt;, with an additional ability to display ranges of command numbers::&lt;/p&gt;
&lt;div class="e2-code-block" data-language="shell"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-shell"&gt;fc -l -20 # show the last 20 commands
fc -l 100 150 # show commands 100 to 150&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2&gt;5. Reverse-i-search&lt;/h2&gt;
&lt;p&gt;This is the most powerful approach. Press “Ctrl+R” to enter reverse incremental search mode. Initially you get no output; start writing any part of a command you remember, e. g. &lt;span class="inline-code"&gt;ssh&lt;/span&gt; or &lt;span class="inline-code"&gt;input.json&lt;/span&gt; or &lt;span class="inline-code"&gt;-n 10&lt;/span&gt; — and you will see the first full command entry with that match!&lt;/p&gt;
&lt;p&gt;From there, you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Press “Enter” to execute the command immediately&lt;/li&gt;
&lt;li&gt;Use left/right arrow keys to move within a command to edit it, then press “Enter” to execute&lt;/li&gt;
&lt;li&gt;Press “Ctrl+R” again to go to the next, older match&lt;/li&gt;
&lt;li&gt;Press “Ctrl+S” to go to the previous, newer match (see comment below)&lt;/li&gt;
&lt;li&gt;Press up-down arrows to view to nearby entries in history around the match&lt;/li&gt;
&lt;li&gt;Press “Ctrl+C” or “Ctrl+G” to exit the search&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On many systems “Ctrl+S” shortcut will not work, as it is prioritized to pause terminal output (press “Ctrl+Q” to resume). To make it work for reverse-i-search, add &lt;span class="inline-code"&gt;stty -ixon&lt;/span&gt; to your shell config. It will disable “Ctrl+S” / “Ctrl+Q” shortcuts for terminal flow control:&lt;/p&gt;
&lt;div class="e2-code-block" data-language="shell"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;Shell&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-shell"&gt;echo &amp;quot;stty -ixon&amp;quot; &amp;gt;&amp;gt; ~/.bashrc
source ~/.bashrc&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Happy command line manipulation!&lt;/p&gt;
&lt;p&gt;💡 This post has a second part: &lt;a href="https://mishurovsky.com/blog/all/shell-configs-for-better-command-history-search/"&gt;Shell Configs for Better Command History Search&lt;/a&gt;&lt;/p&gt;
</description>
</item>


</channel>
</rss>