{
    "version": "https:\/\/jsonfeed.org\/version\/1.1",
    "title": "Guides",
    "_rss_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.",
    "_rss_language": "en",
    "_itunes_email": "george@mishurovsky.com",
    "_itunes_categories_xml": "",
    "_itunes_image": "https:\/\/mishurovsky.com\/blog\/pictures\/userpic\/userpic-square@2x.jpg?1753619610",
    "_itunes_explicit": "no",
    "home_page_url": "https:\/\/mishurovsky.com\/blog\/?go=tags\/guides\/",
    "feed_url": "https:\/\/mishurovsky.com\/blog\/?go=tags%2Fguides%2Fjson%2F",
    "icon": "https:\/\/mishurovsky.com\/blog\/pictures\/userpic\/userpic@2x.jpg?1753619610",
    "authors": [
        {
            "name": "George Mishurovsky",
            "url": "https:\/\/mishurovsky.com\/blog\/",
            "avatar": "https:\/\/mishurovsky.com\/blog\/pictures\/userpic\/userpic@2x.jpg?1753619610"
        }
    ],
    "items": [
        {
            "id": "17",
            "url": "https:\/\/mishurovsky.com\/blog\/?go=all\/how-to-bulk-rename-some-entity-and-files-in-a-project\/",
            "title": "Renaming Entities Project-Wide with find, grep, sed, and rename",
            "content_html": "<p>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.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/mishurovsky.com\/blog\/pictures\/bulk-rename@2x.png\" width=\"667\" height=\"388\" alt=\"\" \/>\n<\/div>\n<p>Imagine we need to rename every <span class=\"inline-code\">Employee<\/span> to <span class=\"inline-code\">Worker<\/span>. This change should affect both file paths and textual occurrences throughout the project.<\/p>\n<p>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:<\/p>\n<ol start=\"1\">\n<li>We must rename both file names and folder names.<\/li>\n<li>We must rename all code and text occurrences.<\/li>\n<li>Casing must be preserved: <span class=\"inline-code\">Employee → Worker<\/span>, and <span class=\"inline-code\">employee → worker<\/span>.<\/li>\n<li>Both plain and compound usages must be properly renamed: <span class=\"inline-code\">createEmployee → createWorker<\/span>.<\/li>\n<li>Non-code,  non-document files must not be affected (consider binary files which by coincidence might have an <span class=\"inline-code\">...employee...<\/span> fragment inside).<\/li>\n<li>There are folders or files which we would want to omit from renaming (e. g., <span class=\"inline-code\">.git<\/span>).<\/li>\n<li>No IDE provides such functionality, so we cannot rely on existing solutions.<\/li>\n<\/ol>\n<p>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., <span class=\"inline-code\">employee.js<\/span>) or uses exports from a library containing the target name (<span class=\"inline-code\">import type { valuableEmployeee } from ‘employee-js’<\/span>), you’ll have to resolve issues manually after renaming.<\/p>\n<h2>Reviewing Expected Changes<\/h2>\n<p>First, remove any folders and files that are recreated during project builds or setup: <span class=\"inline-code\">build\/<\/span>, <span class=\"inline-code\">dist\/<\/span>, <span class=\"inline-code\">node_modules\/<\/span>, <span class=\"inline-code\">.storybook-static\/<\/span>, etc. This step isn’t strictly necessary, but it can help iterate faster if commands encounter errors.<\/p>\n<p>Now, let’s start with renaming text occurrences by listing all files that might get affected with <span class=\"inline-code\">find<\/span> command. Here I am using <span class=\"inline-code\">-iname<\/span> for case-insensitive search and `-not -path ‘<name>’` syntax to exclude folders we need to protect from changes.<\/p>\n<div class=\"e2-code-block\" data-language=\"sh\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-sh\">find . -not -path &#039;*\/.*&#039; -not -path &#039;src\/protected&#039; -iname &#039;*employee*&#039;<\/code><\/pre><\/div><p>Revise the output: make sure it does not contain folders or files you do not want to be changed.<\/p>\n<p>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.<\/p>\n<div class=\"e2-code-block\" data-language=\"sh\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-sh\">grep -RIn -i --exclude-dir=&#039;*\/.*&#039; --exclude-dir=&#039;src\/protected&#039; &#039;employee&#039; .<\/code><\/pre><\/div><p>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. <span class=\"inline-code\">em#plo#yee<\/span>), so you can revert it later.<\/p>\n<h2>Renaming Text Occurences<\/h2>\n<p>Now we can rename all text occurrences, handling separately each casing. It will require some <span class=\"inline-code\">sed<\/span> magic:<\/p>\n<div class=\"e2-code-block\" data-language=\"sh\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-sh\">LC_CTYPE=UTF-8 find . -type f \\\n  -not -path &#039;*\/.*&#039; \\\n  \\( -name &#039;*.ts&#039; -o -name &#039;*.tsx&#039; -o -name &#039;*.js&#039; -o -name &#039;*.json&#039; \\) \\\n  -exec sed -i &#039;s\/Employee\/Worker\/g; s\/employee\/worker\/g&#039; {} +<\/code><\/pre><\/div><p>There are two important points here. First, <span class=\"inline-code\">LC_CTYPE=UTF-8<\/span> allows us to treat all file characters as UTF-8, even if the situation is different. Without it, <span class=\"inline-code\">sed<\/span> stops when encounters non-UTF-8 characters. Second, we use <span class=\"inline-code\">-o -name ‘*.ext’<\/span> syntax to list file extensions to be affected. This prevents accidental changes of binary or image file contents.<\/p>\n<h2>Renaming Paths<\/h2>\n<p>Hopefully, file content renaming finished successfully. From here we will proceed with renaming of file and folder names. For this we will use <span class=\"inline-code\">rename<\/span> command ingesting <span class=\"inline-code\">find<\/span> output:<\/p>\n<div class=\"e2-code-block\" data-language=\"sh\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-sh\">find . -not -path &#039;*\/.*&#039; -depth -name &#039;*Employee*&#039; \\\n  -exec rename &#039;s\/Employee\/Worker\/g&#039; {} +\nfind . -not -path &#039;*\/.*&#039; -depth -name &#039;*employee*&#039; \\\n  -exec rename &#039;s\/employee\/worker\/g&#039; {} +<\/code><\/pre><\/div><p>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.<\/p>\n<p>And that’s it! ✨<br \/>\nIf you created any specially-renamed entities, rename them back manually. Then run <span class=\"inline-code\">git add<\/span> and <span class=\"inline-code\">git commit<\/span> — git should detect all path renames automatically.<\/p>\n",
            "date_published": "2025-10-20T15:07:33+02:00",
            "date_modified": "2026-03-27T10:25:05+02:00",
            "tags": [
                "guides",
                "refactoring",
                "shell"
            ],
            "image": "https:\/\/mishurovsky.com\/blog\/pictures\/bulk-rename@2x.png",
            "_date_published_rfc2822": "Mon, 20 Oct 2025 15:07:33 +0200",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "17",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/mishurovsky.com\/blog\/pictures\/bulk-rename@2x.png"
                ]
            }
        },
        {
            "id": "11",
            "url": "https:\/\/mishurovsky.com\/blog\/?go=all\/how-to-delete-all-local-git-branches\/",
            "title": "How to Delete All Local Git Branches in One Command",
            "content_html": "<p>First, checkout to main (or any other branch you want to clear from upstream branches).<\/p>\n<p>Now, let’s build the command, step by step.<\/p>\n<ol start=\"1\">\n<li>Check which branches were merged to the current branch:<\/li>\n<\/ol>\n<div class=\"e2-code-block\" data-language=\"sh\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-sh\">git branch --merged<\/code><\/pre><\/div><ol start=\"2\">\n<li>Filter out current branch from the output – it is marked by an asterisk (*):<\/li>\n<\/ol>\n<div class=\"e2-code-block\" data-language=\"sh\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-sh\">git branch --merged | grep -v \\*<\/code><\/pre><\/div><ol start=\"3\">\n<li>Turn the columnar output into a space-separated string:<\/li>\n<\/ol>\n<div class=\"e2-code-block\" data-language=\"sh\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-sh\">git branch --merged | grep -v \\* | xargs<\/code><\/pre><\/div><ol start=\"4\">\n<li>Feed these arguments to the deletion command (passed in the second argument of <span class=\"inline-code\">xargs<\/span>):<\/li>\n<\/ol>\n<div class=\"e2-code-block\" data-language=\"sh\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-sh\">git branch --merged | grep -v \\* | xargs git branch -D<\/code><\/pre><\/div>",
            "date_published": "2025-08-28T13:46:06+02:00",
            "date_modified": "2025-10-03T23:37:09+02:00",
            "tags": [
                "git",
                "guides",
                "shell"
            ],
            "_date_published_rfc2822": "Thu, 28 Aug 2025 13:46:06 +0200",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "11",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": true,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "7",
            "url": "https:\/\/mishurovsky.com\/blog\/?go=all\/enforce-any-code-style-constraint-with-eslint\/",
            "title": "Enforce Any Code Style Constraint with ESLint",
            "content_html": "<div class=\"e2-text-picture\">\n<img src=\"https:\/\/mishurovsky.com\/blog\/pictures\/custom-eslint-rule@2x.jpg\" width=\"872\" height=\"577\" alt=\"\" \/>\n<\/div>\n<p>When managing big software projects, it is important to configure code rules for loads of scenarios. Usually, you start with basics, like prohibiting unused variables, requiring <span class=\"inline-code\">super()<\/span> calls in subclass constructors, or catching duplicate conditions in if-else blocks.<\/p>\n<p>In JavaScript \/ TypeScript projects that is handled by standard ESLint plugins: @eslint\/js, typescript-eslint, eslint-plugin-react, etc. They are more or less easy to configure (ignoring the new flat config which adds the fun of guessing which plugins support it and which don’t yet), and this is where most tech leads stop.<\/p>\n<p>However, the bigger the project, the more dependencies and opinionated patterns of doing something it accumulates. Large packages may have separate ESLint plugins maintained by independent contributors, but sometimes you’ll want to enforce a rule that doesn’t exist in any plugin. This becomes very important when you expect many people to work on the project, or if you want to use AI agents to write acceptable production code without thorough manual review.<\/p>\n<p>The good news: <b>ESLint lets you create almost any rule you can imagine!<\/b> You don’t need to know specifics of ESLint scripting, since ChatGPT successfully manages to write 95% of the logic, and the remaining 5% would be easy finish when you see selector structure and regex patterns.<\/p>\n<p>Here, I want to share a specific example. On my current frontend project we use Typescript, React, and Next.js with zustand for client-side state storage. To persist state after a page reload, I added the <span class=\"inline-code\">persist<\/span> plugin that writes and reads data from browser’s <span class=\"inline-code\">localStorage<\/span>. The problem is, Next.js renders client-side components twice: first time on server, then in browser, and <b>both renders must match<\/b>. However, the server doesn’t have access to client data, and the store state differs between environments, unless it was not used before.<\/p>\n<p>The fix is to run rendering with an empty store, and then reading the state specifically on client side. In this case, both server and browser use the same empty state during the first render. This is achieved by using a custom hook, <span class=\"inline-code\">useStore<\/span>, which returns an empty initial state and loads the actual state on client inside a <span class=\"inline-code\">useEffect<\/span>:<\/p>\n<div class=\"e2-code-block\" data-language=\"typescript\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">TypeScript<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-typescript\">export function useStore&lt;T, F&gt;(\n  store: (callback: (state: T) =&gt; unknown) =&gt; unknown,\n  callback: (state: T) =&gt; F,\n) {\n  const result = store(callback) as F;\n  const [data, setData] = useState&lt;F | undefined&gt;(undefined);\n\n  useEffect(() =&gt; {\n    setData(result);\n  }, [result]);\n\n  return data;\n}<\/code><\/pre><\/div><p>Now state can be retrieved like this:<\/p>\n<div class=\"e2-code-block\" data-language=\"typescript\" data-long><div class=\"e2-code-header\"><span class=\"e2-code-language\">TypeScript<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-typescript\">import {uiStore} from &#039;@\/store\/ui&#039;;\nimport {useStore} from &#039;@\/hooks\/useStore&#039;;\n\nconst visiblePanels = useStore(uiStore, (state) =&gt; state.dashboard.visiblePanels); ✅ correct\nconst visiblePanels = uiStore((state) =&gt; state.dashboard.visiblePanels); \/\/ ❌ wrong!<\/code><\/pre><\/div><p>A problem is, nothing in this setup stops someone from using the wrong pattern. Moreover, the wrong way is the default in normal conditions, so any new developer or AI model are likely to use it. Here is where ESLint magic comes useful:<\/p>\n<div class=\"e2-code-block\" data-language=\"javascript\" data-long><div class=\"e2-code-header\"><span class=\"e2-code-language\">JavaScript<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-javascript\">\/\/ eslint.config.mjs\n\nexport default defineConfig([\n  \/\/ ...necessary plugins here...\n  {\n    rules: {\n      &#039;no-restricted-syntax&#039;: [\n        &#039;error&#039;,\n        {\n          selector: &quot;CallExpression[callee.type=&#039;Identifier&#039;][callee.name=\/^(?!use).*Store$\/]&quot;,\n          message: &#039;Do not call store functions directly — use useStore(store, selector) from @\/hooks instead.&#039;,\n        },\n      ],\n    },\n  },\n]);<\/code><\/pre><\/div><p>This way we tell ESLint to watch for any invocation of functions whose name ends with a <span class=\"inline-code\">Store<\/span>, except for <span class=\"inline-code\">useStore<\/span> — our custom hook. Direct usage will be flagged, so the new devs or models will be able to correct themselves. Surely, someone could write a store with a name different from <span class=\"inline-code\">useSomethingStore<\/span>, but this naming format is common and default by docs, so we stay on a safe ground here.<\/p>\n<p>With this approach, you can enforce any code style, variable usage rule, import restriction, or architectural constraint. Add them, use them, and may your code be impossible to write in a wrong way.<\/p>\n",
            "date_published": "2025-08-13T00:06:32+02:00",
            "date_modified": "2025-10-21T11:29:43+02:00",
            "tags": [
                "code style",
                "eslint",
                "guides",
                "javascript",
                "rules",
                "tech lead",
                "typescript",
                "zustand"
            ],
            "image": "https:\/\/mishurovsky.com\/blog\/pictures\/custom-eslint-rule@2x.jpg",
            "_date_published_rfc2822": "Wed, 13 Aug 2025 00:06:32 +0200",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "7",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/mishurovsky.com\/blog\/pictures\/custom-eslint-rule@2x.jpg"
                ]
            }
        },
        {
            "id": "6",
            "url": "https:\/\/mishurovsky.com\/blog\/?go=all\/shell-configs-for-better-command-history-search\/",
            "title": "Shell Configs for Better Command History Search",
            "content_html": "<div class=\"e2-text-picture\">\n<img src=\"https:\/\/mishurovsky.com\/blog\/pictures\/better-shell-search@2x.jpg\" width=\"766\" height=\"381\" alt=\"\" \/>\n<\/div>\n<p>In continuation of my previous post, <a href=\"https:\/\/mishurovsky.com\/blog\/all\/how-to-quickly-retrieve-past-terminal-commands\/\" class=\"nu\">“<u>How to View Past Terminal Commands: from Simple to Robust<\/u>”<\/a>, I want to share shell config settings that help finding commands faster, while also looking for them much more further in the past.<\/p>\n<h2>Increase History Limits<\/h2>\n<p>First, <span class=\"inline-code\">HISTSIZE<\/span> and <span class=\"inline-code\">HISTFILESIZE<\/span>. These settings control how many past commands are stored in session memory and in the history file, respectively. Their defaults are 1000 commands for <span class=\"inline-code\">HISTSIZE<\/span> and 2000 commands for <span class=\"inline-code\">HISTFILESIZE<\/span>.<\/p>\n<p>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 <span class=\"inline-code\">history<\/span> command without additional tricks.<\/p>\n<p>Let’s increase the limits:<\/p>\n<div class=\"e2-code-block\" data-language=\"sh\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-sh\"># bash\nHISTSIZE=50000\nHISTFILESIZE=50000\n\n# zsh\nHISTSIZE=50000\nSAVEHIST=50000<\/code><\/pre><\/div><h2>Remove Duplicates in Search<\/h2>\n<p>Now, let’s compress history entries. When I lookup commands using reverse search (Ctrl+R), I do not want to see duplicates like <span class=\"inline-code\">docker build<\/span>. Let’s keep only the most recent copy of each command:<\/p>\n<div class=\"e2-code-block\" data-language=\"sh\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-sh\"># bash\nHISTCONTROL=ignoredups:erasedups\n\n# zsh\nsetopt HIST_IGNORE_ALL_DUPS<\/code><\/pre><\/div><h2>Ignore Noise in History<\/h2>\n<p>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 <span class=\"inline-code\">ls<\/span> or <span class=\"inline-code\">cd<\/span>. Let’s prevent them from being saved at all:<\/p>\n<div class=\"e2-code-block\" data-language=\"sh\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-sh\"># bash\nHISTIGNORE=&quot;ls:cd:cd -:pwd:exit:clear&quot;\n\n# zsh\nHIST_SKIP_PATTERN=&#039;^(cd|ls|pwd|clear|exit)(\\s|$)&#039;<\/code><\/pre><\/div><h2>Sync History Across Sessions<\/h2>\n<p>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:<\/p>\n<div class=\"e2-code-block\" data-language=\"sh\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-sh\"># bash\nPROMPT_COMMAND=&#039;history -a; history -n&#039;\nshopt -s histappend\n\n# zsh\nsetopt SHARE_HISTORY\nsetopt APPEND_HISTORY\nsetopt INC_APPEND_HISTORY<\/code><\/pre><\/div><h2>Final Setup<\/h2>\n<p>Now we are good! Below are full settings for both bash and zsh. Do not forget to run <span class=\"inline-code\">source ~\/.bashrc<\/span> or <span class=\"inline-code\">source ~\/.zshrc<\/span> after you make the changes.<\/p>\n<div class=\"e2-code-block\" data-language=\"sh\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-sh\"># bash\nHISTSIZE=50000\nHISTFILESIZE=50000\n\nHISTCONTROL=ignoredups:erasedups\nHISTIGNORE=&quot;ls:cd:cd -:pwd:exit:clear&quot;\n\nPROMPT_COMMAND=&#039;history -a; history -n; $PROMPT_COMMAND&#039;\nshopt -s histappend<\/code><\/pre><\/div><div class=\"e2-code-block\" data-language=\"sh\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-sh\"># zsh\nHISTSIZE=50000\nSAVEHIST=50000\n\nsetopt HIST_IGNORE_ALL_DUPS\nHIST_SKIP_PATTERN=&#039;^(cd|ls|pwd|clear|exit)(\\s|$)&#039;\n\nsetopt APPEND_HISTORY\nsetopt SHARE_HISTORY\nsetopt INC_APPEND_HISTORY<\/code><\/pre><\/div><p>Happy command searching!<\/p>\n<h2>P.S. Want Full Command Logging?<\/h2>\n<p>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:<\/p>\n<div class=\"e2-code-block\" data-language=\"sh\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-sh\"># bash\nexport LOGFILE=~\/.full_bash_history.log\nPROMPT_COMMAND=&#039;\n  history -a\n  history -n\n  this_command=$(history 1 | sed &quot;s\/^[ ]*[0-9]*[ ]*\/\/&quot;)\n  echo &quot;$(date &quot;+%Y-%m-%d %H:%M:%S&quot;)  $this_command&quot; &gt;&gt; &quot;$LOGFILE&quot;\n&#039;\n\n# zsh\nfunction preexec() {\n  local LOGFILE=~\/.full_zsh_history.log\n  echo &quot;$(date &#039;+%Y-%m-%d %H:%M:%S&#039;)  $1&quot; &gt;&gt; &quot;$LOGFILE&quot;\n}<\/code><\/pre><\/div>",
            "date_published": "2025-08-07T12:54:33+02:00",
            "date_modified": "2025-10-21T11:29:59+02:00",
            "tags": [
                "guides",
                "search",
                "shell",
                "terminal"
            ],
            "image": "https:\/\/mishurovsky.com\/blog\/pictures\/better-shell-search@2x.jpg",
            "_date_published_rfc2822": "Thu, 07 Aug 2025 12:54:33 +0200",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "6",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/mishurovsky.com\/blog\/pictures\/better-shell-search@2x.jpg"
                ]
            }
        },
        {
            "id": "5",
            "url": "https:\/\/mishurovsky.com\/blog\/?go=all\/how-to-quickly-retrieve-past-terminal-commands\/",
            "title": "How to View Past Terminal Commands — from Simple to Robust",
            "content_html": "<p>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?<\/p>\n<h2>1. The upwards arrow<\/h2>\n<p>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.<\/p>\n<p>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.<\/p>\n<h2>2. Terminal history file<\/h2>\n<p>All the commands you enter into a terminal get stored in <span class=\"inline-code\">.bash_history<\/span> file (or <span class=\"inline-code\">.zsh_history<\/span> if you are on Mac) up to a certain limit. Thus, you can run:<\/p>\n<div class=\"e2-code-block\" data-language=\"shell\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-shell\">cat ~\/.bash_history # output all into the terminal\nless ~\/.bash_history # or use any text viewer\ntail -n 20 ~\/.bash_history # or observe only the most recent n lines\ncat ~\/.bash_history | grep whatever # to search for specific patterns<\/code><\/pre><\/div><p>This method gives you full access to your history file, and lets you search more flexibly.<\/p>\n<h2>3. <span class=\"inline-code\">history<\/span> command<\/h2>\n<p>Almost the same as using the history file directly: you get a list commands, but now it is <i>numbered<\/i>.<\/p>\n<div class=\"e2-code-block\" data-language=\"shell\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-shell\">history -20 # show the last 20 commands\nhistory -500 | grep ssh # search for a specific patter in a command\n!780 # execute command with order number 780<\/code><\/pre><\/div><p>But there is one important difference from direct usage of <span class=\"inline-code\">.bash_history<\/span>:<\/p>\n<ul>\n<li><span class=\"inline-code\">history<\/span> command uses the last <span class=\"inline-code\">HISTSIZE<\/span> history entries (default 1000)<\/li>\n<li><span class=\"inline-code\">.bash_history<\/span> file uses the last <span class=\"inline-code\">HISTFILESIZE<\/span> entries (default 2000)<br \/>\nSo, if your command was run a really long time ago, <span class=\"inline-code\">history<\/span> may not find it, but direct inspection of <span class=\"inline-code\">.bash_history<\/span> can do.<\/li>\n<\/ul>\n<h2>4. <span class=\"inline-code\">fc -l<\/span> command<\/h2>\n<p>This command behaves very similar to <span class=\"inline-code\">history<\/span>, with an additional ability to display ranges of command numbers::<\/p>\n<div class=\"e2-code-block\" data-language=\"shell\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-shell\">fc -l -20 # show the last 20 commands\nfc -l 100 150 # show commands 100 to 150<\/code><\/pre><\/div><h2>5. Reverse-i-search<\/h2>\n<p>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. <span class=\"inline-code\">ssh<\/span> or <span class=\"inline-code\">input.json<\/span> or <span class=\"inline-code\">-n 10<\/span> — and you will see the first full command entry with that match!<\/p>\n<p>From there, you can:<\/p>\n<ul>\n<li>Press “Enter” to execute the command immediately<\/li>\n<li>Use left\/right arrow keys to move within a command to edit it, then press “Enter” to execute<\/li>\n<li>Press “Ctrl+R” again to go to the next, older match<\/li>\n<li>Press “Ctrl+S” to go to the previous, newer match (see comment below)<\/li>\n<li>Press up-down arrows to view to nearby entries in history around the match<\/li>\n<li>Press “Ctrl+C” or “Ctrl+G” to exit the search<\/li>\n<\/ul>\n<p>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 <span class=\"inline-code\">stty -ixon<\/span> to your shell config. It will disable “Ctrl+S” \/ “Ctrl+Q” shortcuts for terminal flow control:<\/p>\n<div class=\"e2-code-block\" data-language=\"shell\"><div class=\"e2-code-header\"><span class=\"e2-code-language\">Shell<\/span><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\"><span class=\"e2-svgi\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 16 16\"><mask id=\"cutout\"><rect width=\"100%\" height=\"100%\" fill=\"white\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" stroke-width=\"1.33\" rx=\"1\" fill=\"black\" stroke=\"black\"\/><\/mask><rect x=\"5.25\" y=\"1.75\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\" mask=\"url(#cutout)\"\/><rect x=\"1.75\" y=\"5.25\" width=\"9\" height=\"9\" rx=\"1\" stroke-width=\"1.33\" fill=\"none\"\/><\/svg>\r\n<\/span>Copy<\/button><\/div><pre><code class=\"hljs language-shell\">echo &quot;stty -ixon&quot; &gt;&gt; ~\/.bashrc\nsource ~\/.bashrc<\/code><\/pre><\/div><p>Happy command line manipulation!<\/p>\n<p>💡 This post has a second part: <a href=\"https:\/\/mishurovsky.com\/blog\/all\/shell-configs-for-better-command-history-search\/\">Shell Configs for Better Command History Search<\/a><\/p>\n",
            "date_published": "2025-08-03T14:18:24+02:00",
            "date_modified": "2025-10-21T11:41:00+02:00",
            "tags": [
                "devops",
                "guides",
                "linux",
                "mac",
                "search",
                "shell",
                "terminal"
            ],
            "_date_published_rfc2822": "Sun, 03 Aug 2025 14:18:24 +0200",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "5",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        }
    ],
    "_e2_version": 4134,
    "_e2_ua_string": "Aegea 11.3 (v4134e)"
}