learnbyexampleLearn Python, Regex, Linux, Scripting, Vim, Ebooks, Self-Publishing and Interesting Tech Nuggets.Zola2025-11-26T00:00:00+00:00https://learnbyexample.github.io/atom.xmlFestive offers for books on Python, Linux, Regular Expressions, Vim and more!2025-11-21T00:00:00+00:002025-11-26T00:00:00+00:00https://learnbyexample.github.io/programming-deals-2025/<p>Hello!</p>
<p>Here are some awesome deals for programming books and courses during the 2025 festive season.</p>
<span id="continue-reading"></span><br>
<h2 id="my-ebooks">My ebooks<a class="zola-anchor" href="#my-ebooks" aria-label="Anchor link for: my-ebooks">🔗</a></h2>
<p>Festive offers for my ebooks till 30-November-2025:</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer">All 13 Books Bundle</a> — $16 (normal price $36), learn Regular Expressions, Linux CLI tools, Python, Vim and more!</li>
<li><a href="https://learnbyexample.gumroad.com/l/py_regex">Understanding Python re(gex)?</a> — FREE (normal price $10)</li>
</ul>
<p align="center"><a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer"><img src="/images/books/all_books_bundle.png" alt="All books bundle" loading="lazy" /></a></p>
<br>
<h2 id="other-deals">Other deals<a class="zola-anchor" href="#other-deals" aria-label="Anchor link for: other-deals">🔗</a></h2>
<ul>
<li><a href="https://treyhunner.com/2025/11/python-black-friday-and-cyber-monday-sales-2025/">Python related deals</a></li>
<li><a href="https://media.pragprog.com/newsletters/2025-11-19.html">The Pragmatic Bookshelf</a> — 50% off</li>
<li><a href="https://adamj.eu/tech/2025/11/20/django-black-friday-deals-2025/">Deals on Django and Git books/software</a></li>
<li><a href="https://github.com/trungdq88/Awesome-Black-Friday-Cyber-Monday">Huge list of awesome deals</a> — tools, productivity, books, courses, etc</li>
<li><a href="https://blackfridaydeals.dev/">blackfridaydeals.dev</a> — Hottest Black Friday Deals for Developers</li>
</ul>
<br>
<p>Happy learning :)</p>
Connect Four game with a twist2025-08-20T00:00:00+00:002025-08-26T00:00:00+00:00https://learnbyexample.github.io/connect-four-game-with-a-twist/<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/ConnectSquare/connect_square.png" alt="Sample screenshot for Connect Square game" /></p>
<span id="continue-reading"></span><br>
<p>From <a href="https://en.wikipedia.org/wiki/Connect_Four">wikipedia: Connect Four</a>:</p>
<blockquote>
<p>Connect Four is a game in which the players choose a color and then take turns dropping colored tokens into a six-row, seven-column vertically suspended grid. The pieces fall straight down, occupying the lowest available space within the column. The objective of the game is to be the first to form a horizontal, vertical, or diagonal line of four of one's own tokens.</p>
</blockquote>
<p>As a twist, this TUI implementation also offers two more variations of the game:</p>
<ul>
<li>form a square, i.e. four cells forming 90 degree angles and equidistant from each other</li>
<li>form a line or square</li>
</ul>
<br>
<h2 id="installation">Installation<a class="zola-anchor" href="#installation" aria-label="Anchor link for: installation">🔗</a></h2>
<p>This app is available on PyPI as <a href="https://pypi.org/project/connectsquare/">connectsquare</a>. Example installation instructions are shown below, adjust them based on your preferences and OS.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># virtual environment
</span><span style="color:#5597d6;">$</span><span> python3</span><span style="color:#5597d6;"> -m</span><span> venv textual_apps
</span><span style="color:#5597d6;">$</span><span> cd textual_apps
</span><span style="color:#5597d6;">$</span><span> source bin/activate
</span><span style="color:#5597d6;">$</span><span> pip install connectsquare
</span><span>
</span><span style="color:#7f8989;"># launch the app
</span><span style="color:#5597d6;">$</span><span> connectsquare
</span></code></pre>
<p>To run the app without having to enter the virtual environment again, add this alias to <code>.bashrc</code> (or equivalent):</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># you'll have to change the path
</span><span style="color:#b39f04;">alias </span><span style="color:#c23f31;">connectsquare</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'/path/to/textual_apps/bin/connectsquare'
</span></code></pre>
<p>As an alternative to manually managing such virtual environments, you can use <a href="https://github.com/astral-sh/uv">uv</a> or <a href="https://github.com/pypa/pipx">pipx</a> instead.</p>
<p>As yet another alternative, you can install <code>textual==0.85.2</code> (see <a href="https://textual.textualize.io/getting_started/">Textual documentation</a> for more details), clone <a href="https://github.com/learnbyexample/TUI-apps">this repository</a> and run the <code>connect_square.py</code> file.</p>
<br>
<h2 id="screenshots">Screenshots<a class="zola-anchor" href="#screenshots" aria-label="Anchor link for: screenshots">🔗</a></h2>
<p>Adjust your terminal's dimension for the game widgets to appear properly, for example 80x30 (characters x lines). Sample screenshots are shown below:</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/ConnectSquare/connect_four.png" alt="Sample screenshot for Connect Four game" /></p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/ConnectSquare/connect_both.png" alt="Sample screenshot for Connect Four or Square game" /></p>
<br>
<h2 id="guide">Guide<a class="zola-anchor" href="#guide" aria-label="Anchor link for: guide">🔗</a></h2>
<ul>
<li>Press the <strong>n</strong> key to start a new game. Existing game, if any, will be abandoned</li>
<li>You can choose between <strong>Connect Four</strong>, <strong>Connect Square</strong> (default) and <strong>Both</strong> types of game</li>
<li>You can choose between <strong>Easy</strong> (default), <strong>Medium</strong> and <strong>Hard</strong> difficulty modes:
<ul>
<li>In the <em>Easy</em> mode, the AI will make a random move</li>
<li>In the <em>Medium</em> mode, the AI will make a random move based on certain weight calculations</li>
<li>In the <em>Hard</em> mode, the AI will make the best move based on the weight calculations (the algorithm is based only on the current board state and thus it is not impossible for the user to win)</li>
</ul>
</li>
<li>The first move is based on the <strong>User first</strong> (default) and <strong>AI first</strong> choices</li>
<li>Only the bottom most empty cell of each column will be considered as a valid move</li>
<li>Press the <strong>t</strong> key to toggle between light and dark themes</li>
<li>Press the <strong>q</strong> key to quit the app</li>
</ul>
<p>User moves are denoted by the ⭕️ character and AI moves are denoted by the ✖️ character.</p>
<p>The text panel under the game board displays the current status of the game. If the game ends with one of the players forming a valid line or square, the cells forming the winning move will be highlighted.</p>
<h2 id="square-tic-tac-toe">Square tic tac toe<a class="zola-anchor" href="#square-tic-tac-toe" aria-label="Anchor link for: square-tic-tac-toe">🔗</a></h2>
<p>If you liked this game, you might also enjoy <a href="https://github.com/learnbyexample/TUI-apps/tree/main/SquareTicTacToe">Square tic tac toe</a>.</p>
<p>If you are interested in learning more about the AI algorithm for the Connect Square game, check out my <a href="https://learnbyexample.github.io/practice_python_projects/square_tic_tac_toe/square_tic_tac_toe_ai.html">explanation here for Square tic tac toe</a> — while there are a few differences between the two, the foundation is the same.</p>
Python regular expression cheatsheet and examples2025-06-09T00:00:00+00:002025-06-09T00:00:00+00:00https://learnbyexample.github.io/python-regex-cheatsheet/<p>This blog post gives an overview and examples of regular expression syntax as implemented by the <code>re</code> built-in module (Python 3.13+). Assume ASCII character set unless otherwise specified. This post is an excerpt from my <a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> book.</p>
<p align="center"><img src="/images/books/pyregex_example.png" alt="Railroad visualization for the regular expression \bpar(en|ro)?t\b" /></p>
<p><em>Visualization created using</em> <a href="https://www.debuggex.com">debuggex</a> <em>for the pattern</em> <code>r'\bpar(en|ro)?t\b'</code></p>
<span id="continue-reading"></span><br>
<p>From <a href="https://docs.python.org/3/library/re.html">docs.python: re</a>:</p>
<blockquote>
<p>A regular expression (or RE) specifies a set of strings that matches it; the functions in this module let you check if a particular string matches a given regular expression</p>
</blockquote>
<br>
<h2 id="elements-that-define-a-regular-expression">Elements that define a regular expression<a class="zola-anchor" href="#elements-that-define-a-regular-expression" aria-label="Anchor link for: elements-that-define-a-regular-expression">🔗</a></h2>
<table><thead><tr><th>Anchors</th><th>Description</th></tr></thead><tbody>
<tr><td><code>\A</code></td><td>restricts the match to the start of string</td></tr>
<tr><td><code>\Z</code></td><td>restricts the match to the end of string</td></tr>
<tr><td><code>^</code></td><td>restricts the match to the start of line</td></tr>
<tr><td><code>$</code></td><td>restricts the match to the end of line</td></tr>
<tr><td><code>\n</code></td><td>newline character is used as the line separator</td></tr>
<tr><td><code>re.MULTILINE</code> or <code>re.M</code></td><td>flag to treat input as multiline string</td></tr>
<tr><td><code>\b</code></td><td>restricts the match to the start/end of words</td></tr>
<tr><td></td><td>word characters: alphabets, digits, underscore</td></tr>
<tr><td><code>\B</code></td><td>matches wherever <code>\b</code> doesn't match</td></tr>
</tbody></table>
<p><code>^</code>, <code>$</code> and <code>\</code> are metacharacters in the above table, as these characters have special meaning. Prefix a <code>\</code> character to remove the special meaning and match such characters literally. For example, <code>\^</code> will match a <code>^</code> character instead of acting as an anchor.</p>
<br>
<table><thead><tr><th>Feature</th><th>Description</th></tr></thead><tbody>
<tr><td><code>|</code></td><td>multiple RE combined as conditional OR</td></tr>
<tr><td></td><td>each alternative can have independent anchors</td></tr>
<tr><td><code>(pat)</code></td><td>group patterns, also a capturing group</td></tr>
<tr><td></td><td><code>a(b|c)d</code> is same as <code>abd|acd</code></td></tr>
<tr><td><code>(?:pat)</code></td><td>non-capturing group</td></tr>
<tr><td><code>(?P<name>pat)</code></td><td>named capture group</td></tr>
<tr><td><code>.</code></td><td>Match any character except the newline character <code>\n</code></td></tr>
<tr><td><code>[]</code></td><td>Character class, matches one character among many</td></tr>
</tbody></table>
<br>
<table><thead><tr><th>Greedy Quantifiers</th><th>Description</th></tr></thead><tbody>
<tr><td><code>*</code></td><td>Match zero or more times</td></tr>
<tr><td><code>+</code></td><td>Match one or more times</td></tr>
<tr><td><code>?</code></td><td>Match zero or one times</td></tr>
<tr><td><code>{m,n}</code></td><td>Match <code>m</code> to <code>n</code> times (inclusive)</td></tr>
<tr><td><code>{m,}</code></td><td>Match at least <code>m</code> times</td></tr>
<tr><td><code>{,n}</code></td><td>Match up to <code>n</code> times (including <code>0</code> times)</td></tr>
<tr><td><code>{n}</code></td><td>Match exactly <code>n</code> times</td></tr>
<tr><td><code>pat1.*pat2</code></td><td>any number of characters between <code>pat1</code> and <code>pat2</code></td></tr>
<tr><td><code>pat1.*pat2|pat2.*pat1</code></td><td>match both <code>pat1</code> and <code>pat2</code> in any order</td></tr>
</tbody></table>
<p>Greedy here means that the above quantifiers will match as much as possible that'll also honor the overall RE. Appending a <code>?</code> to greedy quantifiers makes them <strong>non-greedy</strong>, i.e. match as <em>minimally</em> as possible. Appending a <code>+</code> to greedy quantifiers makes them <strong>possessive</strong>, which prevents backtracking. You can also use <code>(?>pat)</code> <strong>atomic grouping</strong> to safeguard from backtracking. Quantifiers can be applied to literal characters, groups, backreferences and character classes.</p>
<br>
<table><thead><tr><th>Character class</th><th>Description</th></tr></thead><tbody>
<tr><td><code>[aeiou]</code></td><td>Match any vowel</td></tr>
<tr><td><code>[^aeiou]</code></td><td><code>^</code> inverts selection, so this matches any consonant</td></tr>
<tr><td><code>[a-f]</code></td><td><code>-</code> defines a range, so this matches any of abcdef characters</td></tr>
<tr><td><code>\d</code></td><td>Match a digit, same as <code>[0-9]</code></td></tr>
<tr><td><code>\D</code></td><td>Match non-digits, same as <code>[^0-9]</code> or <code>[^\d]</code></td></tr>
<tr><td><code>\w</code></td><td>Match word characters, same as <code>[a-zA-Z0-9_]</code></td></tr>
<tr><td><code>\W</code></td><td>Match non-word characters, same as <code>[^a-zA-Z0-9_]</code> or <code>[^\w]</code></td></tr>
<tr><td><code>\s</code></td><td>Match whitespace characters, same as <code>[\ \t\n\r\f\v]</code></td></tr>
<tr><td><code>\S</code></td><td>Match non-whitespace characters, same as <code>[^\ \t\n\r\f\v]</code> or <code>[^\s]</code></td></tr>
</tbody></table>
<br>
<table><thead><tr><th>Lookarounds</th><th>Description</th></tr></thead><tbody>
<tr><td>lookarounds</td><td>custom assertions, zero-width like anchors</td></tr>
<tr><td><code>(?!pat)</code></td><td>negative lookahead assertion</td></tr>
<tr><td><code>(?<!pat)</code></td><td>negative lookbehind assertion</td></tr>
<tr><td><code>(?=pat)</code></td><td>positive lookahead assertion</td></tr>
<tr><td><code>(?<=pat)</code></td><td>positive lookbehind assertion</td></tr>
<tr><td><code>(?!pat1)(?=pat2)</code></td><td>multiple assertions can be specified in any order</td></tr>
<tr><td></td><td>as they mark a matching location without consuming characters</td></tr>
<tr><td><code>((?!pat).)*</code></td><td>Negate a grouping, similar to negated character class</td></tr>
</tbody></table>
<br>
<table><thead><tr><th>Flags</th><th>Description</th></tr></thead><tbody>
<tr><td><code>re.IGNORECASE</code> or <code>re.I</code></td><td>flag to ignore case</td></tr>
<tr><td><code>re.DOTALL</code> or <code>re.S</code></td><td>allow <code>.</code> metacharacter to match newline characters</td></tr>
<tr><td><code>flags=re.S|re.I</code></td><td>multiple flags can be combined using <code>|</code> operator</td></tr>
<tr><td><code>re.MULTILINE</code> or <code>re.M</code></td><td>allow <code>^</code> and <code>$</code> anchors to match line wise</td></tr>
<tr><td><code>re.VERBOSE</code> or <code>re.X</code></td><td>allows to use literal whitespaces for aligning purposes</td></tr>
<tr><td></td><td>and to add comments after the <code>#</code> character</td></tr>
<tr><td></td><td>escape spaces and <code>#</code> if needed as part of actual RE</td></tr>
<tr><td><code>re.ASCII</code> or <code>re.A</code></td><td>match only ASCII characters for <code>\b</code>, <code>\w</code>, <code>\d</code>, <code>\s</code></td></tr>
<tr><td></td><td>and their opposites, applicable only for Unicode patterns</td></tr>
<tr><td><code>re.LOCALE</code> or <code>re.L</code></td><td>use locale settings for byte patterns and 8-bit locales</td></tr>
<tr><td><code>(?#comment)</code></td><td>another way to add comments (not a flag)</td></tr>
<tr><td><code>(?flags:pat)</code></td><td>inline flags only for this <code>pat</code>, overrides <code>flags</code> argument</td></tr>
<tr><td></td><td>flags is <code>i</code> for <code>re.I</code>, <code>s</code> for <code>re.S</code>, etc, except <code>L</code> for <code>re.L</code></td></tr>
<tr><td><code>(?-flags:pat)</code></td><td>negate flags only for this <code>pat</code></td></tr>
<tr><td><code>(?flags-flags:pat)</code></td><td>apply and negate particular flags only for this <code>pat</code></td></tr>
<tr><td><code>(?flags)</code></td><td>apply flags for whole RE, can be used only at start of RE</td></tr>
<tr><td></td><td>anchors if any, should be specified after <code>(?flags)</code></td></tr>
</tbody></table>
<br>
<table><thead><tr><th>Matched portion</th><th>Description</th></tr></thead><tbody>
<tr><td><code>re.Match</code> object</td><td>details like matched portions, location, etc</td></tr>
<tr><td><code>m[0]</code> or <code>m.group(0)</code></td><td>entire matched portion of <code>re.Match</code> object <code>m</code></td></tr>
<tr><td><code>m[n]</code> or <code>m.group(n)</code></td><td>matched portion of the <em>n</em>th capture group</td></tr>
<tr><td><code>m.groups()</code></td><td>tuple of all the capture groups' matched portions</td></tr>
<tr><td><code>m.span()</code></td><td>start and end+1 index of the entire matched portion</td></tr>
<tr><td></td><td>pass a number to get span of that particular capture group</td></tr>
<tr><td></td><td>can also use <code>m.start()</code> and <code>m.end()</code></td></tr>
<tr><td><code>\N</code></td><td>backreference, gives matched portion of the <em>N</em>th capture group</td></tr>
<tr><td></td><td>applies to both search and replacement sections</td></tr>
<tr><td></td><td>possible values: <code>\1</code>, <code>\2</code> up to <code>\99</code> provided no more digits</td></tr>
<tr><td><code>\g<N></code></td><td>backreference, gives matched portion of the Nth capture group</td></tr>
<tr><td></td><td>possible values: <code>\g<0></code>, <code>\g<1></code>, etc (not limited to 99)</td></tr>
<tr><td></td><td><code>\g<0></code> refers to the entire matched portion</td></tr>
<tr><td><code>(?P<name>pat)</code></td><td>named capture group</td></tr>
<tr><td></td><td>refer as <code>'name'</code> in <code>re.Match</code> object</td></tr>
<tr><td></td><td>refer as <code>(?P=name)</code> in search section</td></tr>
<tr><td></td><td>refer as <code>\g<name></code> in replacement section</td></tr>
<tr><td><code>groupdict</code></td><td>method applied on a <code>re.Match</code> object</td></tr>
<tr><td></td><td>gives named capture group portions as a <code>dict</code></td></tr>
</tbody></table>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> <code>\0</code> and <code>\100</code> onwards are considered as octal values, hence cannot be used as backreferences.</p>
</blockquote>
<h2 id="re-module-functions">re module functions<a class="zola-anchor" href="#re-module-functions" aria-label="Anchor link for: re-module-functions">🔗</a></h2>
<table><thead><tr><th>Function</th><th>Description</th></tr></thead><tbody>
<tr><td><code>re.search</code></td><td>Check if given pattern is present anywhere in input string</td></tr>
<tr><td></td><td>Output is a <code>re.Match</code> object, usable in conditional expressions</td></tr>
<tr><td></td><td>r-strings preferred to define RE</td></tr>
<tr><td></td><td>Use byte pattern for byte input</td></tr>
<tr><td></td><td>Python also maintains a small cache of recent RE</td></tr>
<tr><td><code>re.fullmatch</code></td><td>ensures pattern matches the entire input string</td></tr>
<tr><td><code>re.compile</code></td><td>Compile a pattern for reuse, outputs <code>re.Pattern</code> object</td></tr>
<tr><td><code>re.sub</code></td><td>search and replace</td></tr>
<tr><td><code>re.sub(r'pat', f, s)</code></td><td>function <code>f</code> with <code>re.Match</code> object as the argument</td></tr>
<tr><td><code>re.escape</code></td><td>automatically escape all metacharacters</td></tr>
<tr><td><code>re.split</code></td><td>split a string based on RE</td></tr>
<tr><td></td><td>text matched by the groups will be part of the output</td></tr>
<tr><td></td><td>portion matched by pattern outside group won't be in output</td></tr>
<tr><td><code>re.findall</code></td><td>returns all the matches as a list</td></tr>
<tr><td></td><td>if 1 capture group is used, only its matches are returned</td></tr>
<tr><td></td><td>1+, each element will be tuple of capture groups</td></tr>
<tr><td></td><td>portion matched by pattern outside group won't be in output</td></tr>
<tr><td><code>re.finditer</code></td><td>iterator with <code>re.Match</code> object for each match</td></tr>
<tr><td><code>re.subn</code></td><td>gives tuple of modified string and number of substitutions</td></tr>
</tbody></table>
<p>The function definitions are given below:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span>re.</span><span style="color:#5597d6;">search</span><span>(pattern, string, </span><span style="color:#5597d6;">flags</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">0</span><span>)
</span><span>re.</span><span style="color:#5597d6;">fullmatch</span><span>(pattern, string, </span><span style="color:#5597d6;">flags</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">0</span><span>)
</span><span>re.</span><span style="color:#5597d6;">compile</span><span>(pattern, </span><span style="color:#5597d6;">flags</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">0</span><span>)
</span><span>re.</span><span style="color:#5597d6;">sub</span><span>(pattern, repl, string, </span><span style="color:#5597d6;">count</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#5597d6;">flags</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">0</span><span>)
</span><span>re.</span><span style="color:#5597d6;">escape</span><span>(pattern)
</span><span>re.</span><span style="color:#5597d6;">split</span><span>(pattern, string, </span><span style="color:#5597d6;">maxsplit</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#5597d6;">flags</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">0</span><span>)
</span><span>re.</span><span style="color:#5597d6;">findall</span><span>(pattern, string, </span><span style="color:#5597d6;">flags</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">0</span><span>)
</span><span>re.</span><span style="color:#5597d6;">finditer</span><span>(pattern, string, </span><span style="color:#5597d6;">flags</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">0</span><span>)
</span><span>re.</span><span style="color:#5597d6;">subn</span><span>(pattern, repl, string, </span><span style="color:#5597d6;">count</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#5597d6;">flags</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">0</span><span>)
</span></code></pre>
<br>
<h2 id="regular-expression-examples">Regular expression examples<a class="zola-anchor" href="#regular-expression-examples" aria-label="Anchor link for: regular-expression-examples">🔗</a></h2>
<p>As a good practice, always use <strong>raw strings</strong> to construct RE, unless other formats are required. This will avoid conflict between special meaning of the backslash character in RE and string literals.</p>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> I wrote an interactive TUI app to help you experiment with the examples presented below. See <a href="https://github.com/learnbyexample/TUI-apps/tree/main/PyRegexPlayground">PyRegexPlayground</a> repo for installation instructions and usage guide. See <a href="https://github.com/learnbyexample/TUI-apps/tree/main/PyRegexExercises">PyRegexExercises</a> repo for a TUI app with 100+ Python regex exercises.</p>
</blockquote>
<ul>
<li>examples for <code>re.search()</code></li>
</ul>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>sentence </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'This is a sample string'
</span><span>
</span><span style="color:#7f8989;"># need to load the re module before use
</span><span style="color:#72ab00;">>>> import </span><span>re
</span><span style="color:#7f8989;"># check if 'sentence' contains the pattern described by RE argument
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">is</span><span style="color:#d07711;">'</span><span>, sentence))
</span><span style="color:#b3933a;">True
</span><span>
</span><span style="color:#7f8989;"># ignore case while searching for a match
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">this</span><span style="color:#d07711;">'</span><span>, sentence, </span><span style="color:#5597d6;">flags</span><span style="color:#72ab00;">=</span><span>re.I))
</span><span style="color:#b3933a;">True
</span><span>
</span><span style="color:#7f8989;"># example for a pattern not found in the input string
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">xyz</span><span style="color:#d07711;">'</span><span>, sentence))
</span><span style="color:#b3933a;">False
</span><span>
</span><span style="color:#7f8989;"># re.search output can be directly used in conditional expressions
</span><span style="color:#72ab00;">>>> if </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">ring</span><span style="color:#d07711;">'</span><span>, sentence):
</span><span style="color:#b3933a;">... </span><span style="color:#b39f04;">print</span><span>(</span><span style="color:#d07711;">'mission success'</span><span>)
</span><span style="color:#b3933a;">...
</span><span>mission success
</span><span>
</span><span style="color:#7f8989;"># use raw byte strings for patterns if input is of byte data type
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">rb</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">is</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">b</span><span style="color:#d07711;">'This is a sample string'</span><span>))
</span><span style="color:#b3933a;">True
</span></code></pre>
<ul>
<li>string and line anchors</li>
</ul>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># match the start of the input string
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\A</span><span style="color:#7c8f4c;">hi</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'hi hello</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">top spot'</span><span>))
</span><span style="color:#b3933a;">True
</span><span>
</span><span style="color:#7f8989;"># match the start of a line
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">^</span><span style="color:#7c8f4c;">top</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'hi hello</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">top spot'</span><span>, </span><span style="color:#5597d6;">flags</span><span style="color:#72ab00;">=</span><span>re.M))
</span><span style="color:#b3933a;">True
</span><span>
</span><span style="color:#7f8989;"># match the end of strings
</span><span style="color:#72ab00;">>>> </span><span>words </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'surrender'</span><span>, </span><span style="color:#d07711;">'up'</span><span>, </span><span style="color:#d07711;">'newer'</span><span>, </span><span style="color:#d07711;">'do'</span><span>, </span><span style="color:#d07711;">'era'</span><span>, </span><span style="color:#d07711;">'eel'</span><span>, </span><span style="color:#d07711;">'pest'</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>[w </span><span style="color:#72ab00;">for </span><span>w </span><span style="color:#72ab00;">in </span><span>words </span><span style="color:#72ab00;">if </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">er</span><span style="color:#72ab00;">\Z</span><span style="color:#d07711;">'</span><span>, w)]
</span><span>[</span><span style="color:#d07711;">'surrender'</span><span>, </span><span style="color:#d07711;">'newer'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># check if there's a whole line 'par'
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">^</span><span style="color:#7c8f4c;">par</span><span style="color:#72ab00;">$</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'spare</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">par</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">dare'</span><span>, </span><span style="color:#5597d6;">flags</span><span style="color:#72ab00;">=</span><span>re.M))
</span><span style="color:#b3933a;">True
</span></code></pre>
<ul>
<li>examples for <code>re.findall()</code></li>
</ul>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># match 'par' with optional 's' at start and optional 'e' at end
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\b</span><span style="color:#7c8f4c;">s</span><span style="color:#72ab00;">?</span><span style="color:#7c8f4c;">pare</span><span style="color:#72ab00;">?\b</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'par spar apparent spare part pare'</span><span>)
</span><span>[</span><span style="color:#d07711;">'par'</span><span>, </span><span style="color:#d07711;">'spar'</span><span>, </span><span style="color:#d07711;">'spare'</span><span>, </span><span style="color:#d07711;">'pare'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># numbers >= 100 with optional leading zeros
</span><span style="color:#7f8989;"># you'd need r'\b0*[1-9]\d{2,}\b' if possessive quantifiers isn't used
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\b</span><span style="color:#7c8f4c;">0</span><span style="color:#72ab00;">*+</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">{3,}\b</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'0501 035 154 12 26 98234'</span><span>)
</span><span>[</span><span style="color:#d07711;">'0501'</span><span>, </span><span style="color:#d07711;">'154'</span><span>, </span><span style="color:#d07711;">'98234'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># if multiple capturing groups are used, each element of output
</span><span style="color:#7f8989;"># will be a tuple of strings of all the capture groups
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">/]</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">)/(</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">/,]</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">),</span><span style="color:#72ab00;">?</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'2020/04,1986/Mar'</span><span>)
</span><span>[(</span><span style="color:#d07711;">'2020'</span><span>, </span><span style="color:#d07711;">'04'</span><span>), (</span><span style="color:#d07711;">'1986'</span><span>, </span><span style="color:#d07711;">'Mar'</span><span>)]
</span><span>
</span><span style="color:#7f8989;"># normal capture group will hinder ability to get the whole match
</span><span style="color:#7f8989;"># non-capturing group to the rescue
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\b</span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">(?:st</span><span style="color:#72ab00;">|</span><span style="color:#7c8f4c;">in)</span><span style="color:#72ab00;">\b</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'cost akin more east run'</span><span>)
</span><span>[</span><span style="color:#d07711;">'cost'</span><span>, </span><span style="color:#d07711;">'akin'</span><span>, </span><span style="color:#d07711;">'east'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># useful for debugging purposes as well
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">:</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#7c8f4c;">:</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'green:3.14:teal::brown:oh!:blue'</span><span>)
</span><span>[</span><span style="color:#d07711;">':3.14:'</span><span>, </span><span style="color:#d07711;">'::'</span><span>, </span><span style="color:#d07711;">':oh!:'</span><span>]
</span></code></pre>
<ul>
<li>examples for <code>re.split()</code></li>
</ul>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># split based on one or more digit characters
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">split</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'Sample123string42with777numbers'</span><span>)
</span><span>[</span><span style="color:#d07711;">'Sample'</span><span>, </span><span style="color:#d07711;">'string'</span><span>, </span><span style="color:#d07711;">'with'</span><span>, </span><span style="color:#d07711;">'numbers'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># split based on digit or whitespace characters
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">split</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">[</span><span style="color:#b3933a;">\d\s</span><span style="color:#aeb52b;">]</span><span style="color:#72ab00;">+</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'**1</span><span style="color:#aeb52b;">\f</span><span style="color:#d07711;">2</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">3star</span><span style="color:#aeb52b;">\t</span><span style="color:#d07711;">7 77</span><span style="color:#aeb52b;">\r</span><span style="color:#d07711;">**'</span><span>)
</span><span>[</span><span style="color:#d07711;">'**'</span><span>, </span><span style="color:#d07711;">'star'</span><span>, </span><span style="color:#d07711;">'**'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># to include the matching delimiter strings as well in the output
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">split</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'Sample123string42with777numbers'</span><span>)
</span><span>[</span><span style="color:#d07711;">'Sample'</span><span>, </span><span style="color:#d07711;">'123'</span><span>, </span><span style="color:#d07711;">'string'</span><span>, </span><span style="color:#d07711;">'42'</span><span>, </span><span style="color:#d07711;">'with'</span><span>, </span><span style="color:#d07711;">'777'</span><span>, </span><span style="color:#d07711;">'numbers'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># multiple capture groups example
</span><span style="color:#7f8989;"># note that the portion matched by b+ isn't present in the output
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">split</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(a</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">)b</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">(c</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'3.14aabccc42'</span><span>)
</span><span>[</span><span style="color:#d07711;">'3.14'</span><span>, </span><span style="color:#d07711;">'aa'</span><span>, </span><span style="color:#d07711;">'ccc'</span><span>, </span><span style="color:#d07711;">'42'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># use non-capturing group if capturing is not needed
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">split</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">hand(?:y</span><span style="color:#72ab00;">|</span><span style="color:#7c8f4c;">ful)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'123handed42handy777handful500'</span><span>)
</span><span>[</span><span style="color:#d07711;">'123handed42'</span><span>, </span><span style="color:#d07711;">'777'</span><span>, </span><span style="color:#d07711;">'500'</span><span>]
</span></code></pre>
<ul>
<li>backreferencing within the search pattern</li>
</ul>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>words </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'effort'</span><span>, </span><span style="color:#d07711;">'flee'</span><span>, </span><span style="color:#d07711;">'facade'</span><span>, </span><span style="color:#d07711;">'oddball'</span><span>, </span><span style="color:#d07711;">'rat'</span><span>, </span><span style="color:#d07711;">'tool'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># whole words that have at least one consecutive repeated character
</span><span style="color:#72ab00;">>>> </span><span>[w </span><span style="color:#72ab00;">for </span><span>w </span><span style="color:#72ab00;">in </span><span>words </span><span style="color:#72ab00;">if </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\b</span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">\w</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">\1</span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">*\b</span><span style="color:#d07711;">'</span><span>, w)]
</span><span>[</span><span style="color:#d07711;">'effort'</span><span>, </span><span style="color:#d07711;">'flee'</span><span>, </span><span style="color:#d07711;">'oddball'</span><span>, </span><span style="color:#d07711;">'tool'</span><span>]
</span></code></pre>
<ul>
<li>working with matched portions</li>
</ul>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># re.Match object
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">so</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">n</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'too soon a song snatch'</span><span>)
</span><span style="color:#72ab00;"><</span><span>re.Match </span><span style="color:#a2a001;">object</span><span>; span</span><span style="color:#72ab00;">=</span><span>(</span><span style="color:#b3933a;">4</span><span>, </span><span style="color:#b3933a;">8</span><span>), match</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'soon'</span><span style="color:#72ab00;">>
</span><span>
</span><span style="color:#7f8989;"># retrieving the entire matched portion, note the use of [0]
</span><span style="color:#72ab00;">>>> </span><span>motivation </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'Doing is often better than thinking of doing.'
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">of</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">ink</span><span style="color:#d07711;">'</span><span>, motivation)[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#d07711;">'often better than think'
</span><span>
</span><span style="color:#7f8989;"># capture group example
</span><span style="color:#72ab00;">>>> </span><span>purchase </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'coffee:100g tea:250g sugar:75g chocolate:50g'
</span><span style="color:#72ab00;">>>> </span><span>m </span><span style="color:#72ab00;">= </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">:(</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#7c8f4c;">)g</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#7c8f4c;">:(</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#7c8f4c;">)g</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#7c8f4c;">chocolate:(</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#7c8f4c;">)g</span><span style="color:#d07711;">'</span><span>, purchase)
</span><span style="color:#7f8989;"># to get the matched portion of the second capture group
</span><span style="color:#72ab00;">>>> </span><span>m[</span><span style="color:#b3933a;">2</span><span>]
</span><span style="color:#d07711;">'250'
</span><span>
</span><span style="color:#7f8989;"># to get a tuple of all the capture groups
</span><span style="color:#72ab00;">>>> </span><span>m.</span><span style="color:#5597d6;">groups</span><span>()
</span><span>(</span><span style="color:#d07711;">'100'</span><span>, </span><span style="color:#d07711;">'250'</span><span>, </span><span style="color:#d07711;">'50'</span><span>)
</span></code></pre>
<ul>
<li>examples for <code>re.finditer()</code></li>
</ul>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># numbers < 350
</span><span style="color:#72ab00;">>>> </span><span>m_iter </span><span style="color:#72ab00;">= </span><span>re.</span><span style="color:#5597d6;">finditer</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'45 349 651 593 4 204 350'</span><span>)
</span><span style="color:#72ab00;">>>> </span><span>[m[</span><span style="color:#b3933a;">0</span><span>] </span><span style="color:#72ab00;">for </span><span>m </span><span style="color:#72ab00;">in </span><span>m_iter </span><span style="color:#72ab00;">if </span><span style="color:#a2a001;">int</span><span>(m[</span><span style="color:#b3933a;">0</span><span>]) </span><span style="color:#72ab00;">< </span><span style="color:#b3933a;">350</span><span>]
</span><span>[</span><span style="color:#d07711;">'45'</span><span>, </span><span style="color:#d07711;">'349'</span><span>, </span><span style="color:#d07711;">'4'</span><span>, </span><span style="color:#d07711;">'204'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># start and end+1 index of each matching portion
</span><span style="color:#72ab00;">>>> </span><span>m_iter </span><span style="color:#72ab00;">= </span><span>re.</span><span style="color:#5597d6;">finditer</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">so</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">n</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'song too soon snatch'</span><span>)
</span><span style="color:#72ab00;">>>> for </span><span>m </span><span style="color:#72ab00;">in </span><span>m_iter:
</span><span style="color:#b3933a;">... </span><span style="color:#b39f04;">print</span><span>(m.</span><span style="color:#5597d6;">span</span><span>())
</span><span style="color:#b3933a;">...
</span><span>(</span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#b3933a;">3</span><span>)
</span><span>(</span><span style="color:#b3933a;">9</span><span>, </span><span style="color:#b3933a;">13</span><span>)
</span></code></pre>
<ul>
<li>examples for <code>re.sub()</code></li>
</ul>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># add something to the start of every line
</span><span style="color:#72ab00;">>>> </span><span>ip_lines </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">"catapults</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">concatenate</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">cat"
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">print</span><span>(re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">^</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'* '</span><span>, ip_lines, </span><span style="color:#5597d6;">flags</span><span style="color:#72ab00;">=</span><span>re.M))
</span><span style="color:#72ab00;">* </span><span>catapults
</span><span style="color:#72ab00;">* </span><span>concatenate
</span><span style="color:#72ab00;">* </span><span>cat
</span><span>
</span><span style="color:#7f8989;"># replace 'par' only at the start of a word
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\b</span><span style="color:#7c8f4c;">par</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'X'</span><span>, </span><span style="color:#d07711;">'par spar apparent spare part'</span><span>)
</span><span style="color:#d07711;">'X spar apparent spare Xt'
</span><span>
</span><span style="color:#7f8989;"># same as: r'part|parrot|parent'
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">par(en</span><span style="color:#72ab00;">|</span><span style="color:#7c8f4c;">ro)</span><span style="color:#72ab00;">?</span><span style="color:#7c8f4c;">t</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'X'</span><span>, </span><span style="color:#d07711;">'par part parrot parent'</span><span>)
</span><span style="color:#d07711;">'par X X X'
</span><span>
</span><span style="color:#7f8989;"># remove first two columns where : is delimiter
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\A</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">:]</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">:)</span><span style="color:#72ab00;">{2}</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">''</span><span>, </span><span style="color:#d07711;">'apple:123:banana:cherry'</span><span>)
</span><span style="color:#d07711;">'banana:cherry'
</span></code></pre>
<ul>
<li>backreferencing in the replacement section</li>
</ul>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># remove any number of consecutive duplicate words separated by space
</span><span style="color:#7f8989;"># use \W+ instead of space to cover cases like 'a;a<-;a'
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\b</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">)( </span><span style="color:#72ab00;">\1</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">+\b</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\1</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'aa a a a 42 f_1 f_1 f_13.14'</span><span>)
</span><span style="color:#d07711;">'aa a 42 f_1 f_13.14'
</span><span>
</span><span style="color:#7f8989;"># add something around the matched strings
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">\g</span><span style="color:#7c8f4c;"><0>0)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'52 apples and 31 mangoes'</span><span>)
</span><span style="color:#d07711;">'(520) apples and (310) mangoes'
</span><span>
</span><span style="color:#7f8989;"># swap words that are separated by a comma
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">),(</span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\2</span><span style="color:#7c8f4c;">,</span><span style="color:#72ab00;">\1</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'good,bad 42,24'</span><span>)
</span><span style="color:#d07711;">'bad,good 24,42'
</span><span>
</span><span style="color:#7f8989;"># example with both capturing and non-capturing groups
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">)(?:abc)</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\2</span><span style="color:#7c8f4c;">:</span><span style="color:#72ab00;">\1</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'1000abcabc42 12abcd21'</span><span>)
</span><span style="color:#d07711;">'42:1000 12abcd21'
</span></code></pre>
<ul>
<li>using functions in the replacement section of <code>re.sub()</code></li>
</ul>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> from </span><span>math </span><span style="color:#72ab00;">import </span><span>factorial
</span><span style="color:#72ab00;">>>> </span><span>numbers </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'1 2 3 4 5'
</span><span style="color:#72ab00;">>>> </span><span style="background-color:#562d56bf;color:#f8f8f8;">def</span><span> </span><span style="color:#5597d6;">fact_num</span><span>(n):
</span><span style="color:#b3933a;">... </span><span style="color:#72ab00;">return </span><span style="color:#a2a001;">str</span><span>(</span><span style="color:#5597d6;">factorial</span><span>(</span><span style="color:#a2a001;">int</span><span>(n[</span><span style="color:#b3933a;">0</span><span>])))
</span><span style="color:#b3933a;">...
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#d07711;">'</span><span>, fact_num, numbers)
</span><span style="color:#d07711;">'1 2 6 24 120'
</span><span>
</span><span style="color:#7f8989;"># using lambda
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#72ab00;">lambda </span><span style="color:#5597d6;">m</span><span>: </span><span style="color:#a2a001;">str</span><span>(</span><span style="color:#5597d6;">factorial</span><span>(</span><span style="color:#a2a001;">int</span><span>(m[</span><span style="color:#b3933a;">0</span><span>]))), numbers)
</span><span style="color:#d07711;">'1 2 6 24 120'
</span></code></pre>
<ul>
<li>examples for lookarounds</li>
</ul>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># change 'cat' only if it is not followed by a digit character
</span><span style="color:#7f8989;"># note that the end of string satisfies the given assertion
</span><span style="color:#7f8989;"># 'catcat' has two matches as the assertion doesn't consume characters
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">cat(</span><span style="color:#aeb52b;">?!\d</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'dog'</span><span>, </span><span style="color:#d07711;">'hey cats! cat42 cat_5 catcat'</span><span>)
</span><span style="color:#d07711;">'hey dogs! cat42 dog_5 dogdog'
</span><span>
</span><span style="color:#7f8989;"># change whole word only if it is not preceded by : or -
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?<![:-]</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">\b</span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">+</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'X'</span><span>, </span><span style="color:#d07711;">':cart <apple -rest ;tea'</span><span>)
</span><span style="color:#d07711;">':cart <X -rest ;X'
</span><span>
</span><span style="color:#7f8989;"># extract digits only if it is preceded by - and followed by ; or :
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?<=</span><span style="color:#7c8f4c;">-)</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?=[:;]</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'42 apple-5, fig3; x-83, y-20: f12'</span><span>)
</span><span>[</span><span style="color:#d07711;">'20'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># words containing 'b' and 'e' and 't' in any order
</span><span style="color:#72ab00;">>>> </span><span>words </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'sequoia'</span><span>, </span><span style="color:#d07711;">'questionable'</span><span>, </span><span style="color:#d07711;">'exhibit'</span><span>, </span><span style="color:#d07711;">'equation'</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>[w </span><span style="color:#72ab00;">for </span><span>w </span><span style="color:#72ab00;">in </span><span>words </span><span style="color:#72ab00;">if </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?=.</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">b)(</span><span style="color:#aeb52b;">?=.</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">e)</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">t</span><span style="color:#d07711;">'</span><span>, w)]
</span><span>[</span><span style="color:#d07711;">'questionable'</span><span>, </span><span style="color:#d07711;">'exhibit'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># match if 'do' is not there between 'at' and 'par'
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">at((</span><span style="color:#aeb52b;">?!</span><span style="color:#7c8f4c;">do)</span><span style="color:#aeb52b;">.</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">par</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'fox,cat,dog,parrot'</span><span>))
</span><span style="color:#b3933a;">False
</span><span style="color:#7f8989;"># match if 'go' is not there between 'at' and 'par'
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">at((</span><span style="color:#aeb52b;">?!</span><span style="color:#7c8f4c;">go)</span><span style="color:#aeb52b;">.</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">par</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'fox,cat,dog,parrot'</span><span>))
</span><span style="color:#b3933a;">True
</span></code></pre>
<ul>
<li>examples for <code>re.compile()</code></li>
</ul>
<p>Regular expressions can be compiled using the <code>re.compile()</code> function, which gives back a <code>re.Pattern</code> object. The top level <code>re</code> module functions are all available as methods for this object. Compiling a regular expression helps if the RE has to be used in multiple places or called upon multiple times inside a loop (speed benefit). By default, Python maintains a small list of recently used RE, so the speed benefit doesn't apply for trivial use cases.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>pet </span><span style="color:#72ab00;">= </span><span>re.</span><span style="color:#5597d6;">compile</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">dog</span><span style="color:#d07711;">'</span><span>)
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">type</span><span>(pet)
</span><span style="color:#72ab00;"><</span><span style="background-color:#562d56bf;color:#f8f8f8;">class</span><span> </span><span style="color:#d07711;">'re.Pattern'</span><span style="color:#72ab00;">>
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(pet.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#d07711;">'They bought a dog'</span><span>))
</span><span style="color:#b3933a;">True
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(pet.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#d07711;">'A cat crossed their path'</span><span>))
</span><span style="color:#b3933a;">False
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>pat </span><span style="color:#72ab00;">= </span><span>re.</span><span style="color:#5597d6;">compile</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\([</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">)]</span><span style="color:#72ab00;">*</span><span style="color:#aeb52b;">\)</span><span style="color:#d07711;">'</span><span>)
</span><span style="color:#72ab00;">>>> </span><span>pat.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#d07711;">''</span><span>, </span><span style="color:#d07711;">'a+b(addition) - foo() + c</span><span style="color:#aeb52b;">%d</span><span style="color:#d07711;">(#modulo)'</span><span>)
</span><span style="color:#d07711;">'a+b - foo + c</span><span style="color:#aeb52b;">%d</span><span style="color:#d07711;">'
</span><span style="color:#72ab00;">>>> </span><span>pat.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#d07711;">''</span><span>, </span><span style="color:#d07711;">'Hi there(greeting). Nice day(a(b)'</span><span>)
</span><span style="color:#d07711;">'Hi there. Nice day'
</span></code></pre>
<br>
<h2 id="understanding-python-re-gex-book">Understanding Python re(gex)? book<a class="zola-anchor" href="#understanding-python-re-gex-book" aria-label="Anchor link for: understanding-python-re-gex-book">🔗</a></h2>
<p>Visit my GitHub repo <a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> for details about the book I wrote on Python regular expressions. The book uses plenty of examples to explain the concepts from the basics and introduces more advanced concepts step-by-step. The book also covers the <a href="https://pypi.org/project/regex/">third-party regex module</a>. The cheatsheet and examples presented in this post are based on the contents of this book.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/py_regular_expressions/master/images/py_regex_ls.png" width="640px" height="360px" alt="Understanding Python re(gex)? cover image" loading="lazy" /></p>
<p>You can get all my ebooks as a single bundle via <a href="https://leanpub.com/b/learnbyexample-all-books">leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/all-books">gumroad</a>.</p>
Better bindings for command line history search2025-05-21T00:00:00+00:002025-05-27T00:00:00+00:00https://learnbyexample.github.io/mini/better-bindings-cli-history-search/<p>Do you find it confusing to use <code>Ctrl+r</code> for searching a command from shell history? I have the following mappings in the <code>~/.inputrc</code> file, so that I can use the <code>↑</code> and <code>↓</code> arrow keys instead. Note that this assumes you are using Emacs-style key bindings instead of Vi mode.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#d07711;">"\e[A"</span><span style="color:#5597d6;">:</span><span> history-search-backward
</span><span style="color:#d07711;">"\e[B"</span><span style="color:#5597d6;">:</span><span> history-search-forward
</span></code></pre>
<p>Normally, when you use the <code>↑</code> arrow key, you'll get the previous command from the history. You can repeatedly press the key to get more entries. With the above settings active, this behavior will change if you have some characters already typed before pressing the arrow key — you'll only get entries from the history that match these characters from the start of the command. If there are multiple matches, you can use the <code>↑</code> and <code>↓</code> keys repeatedly to move backwards and forwards through the list.</p>
<p>If you want the matching to happen anywhere in command (same behavior as <code>Ctrl+r</code> and <code>Ctrl+s</code>), use the following lines instead:</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#d07711;">"\e[A"</span><span style="color:#5597d6;">:</span><span> history-substring-search-backward
</span><span style="color:#d07711;">"\e[B"</span><span style="color:#5597d6;">:</span><span> history-substring-search-forward
</span></code></pre>
<p>The <code>~/.inputrc</code> file affects any shell using the <code>readline</code> library (for example, programming language REPLs). You can use the <code>bind</code> command and put them in, <code>~/.bashrc</code> for example, to affect only that particular shell.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#b39f04;">bind </span><span style="color:#d07711;">'"\e[A": history-search-backward'
</span><span style="color:#b39f04;">bind </span><span style="color:#d07711;">'"\e[B": history-search-forward'
</span></code></pre>
<h2 id="further-reading">Further Reading</h2>
<ul>
<li><a href="https://wiki.archlinux.org/title/Readline">Introduction to readline</a></li>
<li><a href="https://cupfullofcode.com/blog/2013/07/03/efficient-command-line-navigation/index.html">Efficient command line navigation</a></li>
<li><a href="https://github.com/junegunn/fzf">fzf: general purpose command line fuzzy finder</a></li>
<li><a href="https://mikebian.co/supercharged-zsh-command-history/">Supercharged zsh command history</a></li>
</ul>
Customizing pandoc to generate beautiful pdf and epub from markdown2025-05-19T00:00:00+00:002025-06-04T00:00:00+00:00https://learnbyexample.github.io/customizing-pandoc/<p>Either you've already heard of <code>pandoc</code> or if you have searched online for <code>markdown</code> to <code>pdf</code> or similar, you are sure to come across <code>pandoc</code>. This tutorial will help you use <code>pandoc</code> to generate <code>pdf</code> and <code>epub</code> versions from a <a href="https://github.github.com/gfm/">GitHub style markdown</a> file.</p>
<p>The main motivation for this blog post is to highlight the customizations I used for <a href="https://learnbyexample.github.io/books/">self-publishing my ebooks</a>. It wasn't easy to arrive at the setup I ended up with, so I hope this will be useful for those looking to use <code>pandoc</code> for such a purpose. This guide is specifically aimed at technical books that has code snippets.</p>
<p align="center"><img src="/images/pandoc_pdf/customizing_pandoc.png" alt="Customizing pandoc" loading="lazy" /></p>
<p align="center"><i>Poster created using <a href="https://www.canva.com/">Canva</a></i></p>
<span id="continue-reading"></span><br>
<h2 id="installation">Installation<a class="zola-anchor" href="#installation" aria-label="Anchor link for: installation">🔗</a></h2>
<p>If you use a debian based distro like Ubuntu, the below steps are enough for the demos in this tutorial. If you get an error or warning, search that issue online and you'll likely find what else has to be installed.</p>
<p>I first downloaded <code>deb</code> file from <a href="https://github.com/jgm/pandoc/releases">pandoc: releases</a> and installed it. Followed by packages needed for <code>pdf</code> generation.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># latest pandoc version as of 19 May 2025
</span><span style="color:#5597d6;">$</span><span> sudo gdebi </span><span style="color:#5597d6;">~</span><span>/Downloads/pandoc-3.7.0.1-1-amd64.deb
</span><span>
</span><span style="color:#7f8989;"># note that download size is hundreds of MB
</span><span style="color:#5597d6;">$</span><span> sudo apt install texlive-xetex
</span><span style="color:#5597d6;">$</span><span> sudo apt install librsvg2-bin
</span><span style="color:#5597d6;">$</span><span> sudo apt install texlive-science
</span></code></pre>
<p>For more details and instructions for other operating systems, refer to <a href="https://pandoc.org/installing.html">pandoc: installation</a>.</p>
<br>
<h2 id="minimal-example">Minimal example<a class="zola-anchor" href="#minimal-example" aria-label="Anchor link for: minimal-example">🔗</a></h2>
<p>Once <code>pandoc</code> is working on your system, try generating a sample <code>pdf</code> without any customization.</p>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://github.com/learnbyexample/learnbyexample.github.io/tree/master/files/pandoc_pdf">learnbyexample.github.io repo</a> for all the input and output files referred in this tutorial.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> pandoc sample_1.md</span><span style="color:#5597d6;"> -f</span><span> gfm</span><span style="color:#5597d6;"> -o</span><span> sample_1.pdf
</span></code></pre>
<p>Here <code>sample_1.md</code> is input markdown file and <code>-f</code> is used to specify that the input format is GitHub style markdown. The <code>-o</code> option specifies the output file type based on extension. The default output is probably good enough. But I wished to customize hyperlinks, inline code style, add page breaks between chapters, etc. This blog post will discuss these customizations one by one.</p>
<p><img src="/images/info.svg" alt="info" /> <code>pandoc</code> has its own flavor of <code>markdown</code> with many useful extensions — see <a href="https://pandoc.org/MANUAL.html#pandocs-markdown">pandoc: pandocs-markdown</a> for details. GitHub style markdown is recommended if you wish to use the same source (or with minor changes) in multiple places.</p>
<p><img src="/images/info.svg" alt="info" /> It is advised to use <code>markdown</code> headers in order without skipping — for example, <code>H1</code> for chapter heading and <code>H2</code> for chapter sub-section, etc is fine. <code>H1</code> for chapter heading and <code>H3</code> for sub-section is not. Using the former can give automatic index navigation on ebook readers.</p>
<p>On <a href="https://wiki.gnome.org/Apps/Evince">Evince</a> reader, the index navigation for above sample looks like this:</p>
<p align="center"><img src="/images/pandoc_pdf/chapter_index.png" alt="index navigation" loading="lazy" /></p>
<br>
<h2 id="chapter-breaks">Chapter breaks<a class="zola-anchor" href="#chapter-breaks" aria-label="Anchor link for: chapter-breaks">🔗</a></h2>
<p>As observed from the previous demo, there are no chapter breaks by default. Searching for a <a href="https://superuser.com/questions/601469/getting-chapters-to-start-on-a-new-page-in-a-pandoc-generated-pdf">solution online</a>, I got this piece of <code>tex</code> code:</p>
<pre data-lang="latex" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-latex "><code class="language-latex" data-lang="latex"><span style="color:#72ab00;">\usepackage</span><span>{</span><span style="color:#a2a001;">sectsty</span><span>}
</span><span style="color:#b39f04;">\sectionfont</span><span>{</span><span style="color:#b39f04;">\clearpage</span><span>}
</span></code></pre>
<p>This can be added using the <code>-H</code> option. From <code>pandoc</code> manual:</p>
<blockquote>
<p>-H FILE, --include-in-header=FILE</p>
<p>Include contents of FILE, verbatim, at the end of the header. This
can be used, for example, to include special CSS or JavaScript in
HTML documents. This option can be used repeatedly to include multiple
files in the header. They will be included in the order specified.
Implies --standalone.</p>
</blockquote>
<p>The <code>pandoc</code> invocation now looks like:</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> pandoc sample_1.md</span><span style="color:#5597d6;"> -f</span><span> gfm</span><span style="color:#5597d6;"> -H</span><span> chapter_break.tex</span><span style="color:#5597d6;"> -o</span><span> sample_1_chapter_break.pdf
</span></code></pre>
<p>You can add further customization to headings, for example:</p>
<ul>
<li><code>\sectionfont{\underline\clearpage}</code> to underline chapter names</li>
<li><code>\sectionfont{\LARGE\clearpage}</code> to allow chapter names to get even bigger</li>
</ul>
<p>Here are some more links to read about various customizations:</p>
<ul>
<li><a href="https://tex.stackexchange.com/questions/1455/how-to-set-the-font-for-a-section-title-and-chapter-etc">tex.stackexchange: section fonts</a></li>
<li><a href="https://tex.stackexchange.com/questions/230730/section-coming-up-as-undefined-when-using-sectsty">tex.stackexchange: section colors</a></li>
<li><a href="https://tex.stackexchange.com/questions/10138/change-section-fonts">tex.stackexchange: change section fonts</a></li>
</ul>
<br>
<h2 id="changing-settings-via-v-option">Changing settings via -V option<a class="zola-anchor" href="#changing-settings-via-v-option" aria-label="Anchor link for: changing-settings-via-v-option">🔗</a></h2>
<blockquote>
<p>-V KEY[=VAL], --variable=KEY[:VAL]</p>
<p>Set the template variable KEY to the value VAL when rendering the
document in standalone mode. This is generally only useful when the
--template option is used to specify a custom template, since pandoc
automatically sets the variables used in the default templates. If
no VAL is specified, the key will be given the value true.</p>
</blockquote>
<p>The <code>-V</code> option allows to change variable values to customize settings like page size, font, link color, etc. As more settings are changed, better to use a simple script to call <code>pandoc</code> instead of typing the whole command on the terminal.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;">#!/bin/bash
</span><span>
</span><span style="color:#5597d6;">pandoc </span><span style="color:#d07711;">"$</span><span style="color:#acb3c2;">1</span><span style="color:#d07711;">" </span><span>\
</span><span style="color:#5597d6;"> -f</span><span> gfm \
</span><span style="color:#5597d6;"> --include-in-header</span><span> chapter_break.tex \
</span><span style="color:#5597d6;"> -V</span><span> linkcolor:blue \
</span><span style="color:#5597d6;"> -V</span><span> geometry:a4paper \
</span><span style="color:#5597d6;"> -V</span><span> geometry:margin=2cm \
</span><span style="color:#5597d6;"> -V</span><span> mainfont=</span><span style="color:#d07711;">"DejaVu Serif" </span><span>\
</span><span style="color:#5597d6;"> -V</span><span> monofont=</span><span style="color:#d07711;">"DejaVu Sans Mono" </span><span>\
</span><span style="color:#5597d6;"> --pdf-engine</span><span style="color:#72ab00;">=</span><span>xelatex \
</span><span style="color:#5597d6;"> -o </span><span style="color:#d07711;">"$</span><span style="color:#acb3c2;">2</span><span style="color:#d07711;">"
</span></code></pre>
<ul>
<li><code>mainfont</code> is for normal text</li>
<li><code>monofont</code> is for code snippets</li>
<li><code>geometry</code> is for page size and margins</li>
<li><code>linkcolor</code> will set the color for internal links
<ul>
<li>this will also colorize other types of links</li>
<li>set <code>urlcolor</code> if you want to distinguish URLs and so on for other types</li>
</ul>
</li>
<li>to increase the default <strong>font size</strong>, use <code>-V fontsize=12pt</code>
<ul>
<li>See <a href="https://stackoverflow.com/q/23811002/4082052">stackoverflow: change font size</a> if you need even bigger size options</li>
</ul>
</li>
</ul>
<p>Using <code>xelatex</code> as the <code>pdf-engine</code> helps to use any font installed in your system. One reason I chose <code>DejaVu</code> was because it supported <strong>Greek</strong> and other Unicode characters that were causing error with other fonts. See <a href="https://tex.stackexchange.com/questions/21736/using-xelatex-instead-of-pdflatex">tex.stackexchange: Using XeLaTeX instead of pdfLaTeX</a> for some more details.</p>
<p>The <code>pandoc</code> invocation is now through a script:</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> chmod +x md2pdf.sh
</span><span style="color:#5597d6;">$</span><span> ./md2pdf.sh sample_1.md sample_1_settings.pdf
</span></code></pre>
<p>Do compare the pdf generated side by side with previous output before proceeding.</p>
<p><img src="/images/warning.svg" alt="warning" /> On my system, <code>DejaVu Serif</code> did not have <em>italic</em> variation installed, so I had to use <code>sudo apt install ttf-dejavu-extra</code> to get it.</p>
<br>
<h2 id="syntax-highlighting">Syntax highlighting<a class="zola-anchor" href="#syntax-highlighting" aria-label="Anchor link for: syntax-highlighting">🔗</a></h2>
<p>One option to customize syntax highlighting for code snippets is to save one of the <code>pandoc</code> themes and editing it. See <a href="https://stackoverflow.com/a/47876166/4082052">stackoverflow: What are the available syntax highlighters?</a> for available themes and more details (as a good practice on stackoverflow, go through all answers and comments — the linked/related sections on sidebar are useful as well).</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> pandoc</span><span style="color:#5597d6;"> --print-highlight-style</span><span style="color:#72ab00;">=</span><span>pygments </span><span style="color:#72ab00;">></span><span> pygments.theme
</span></code></pre>
<p>Edit the above file to customize the theme. Use sites like <a href="https://www.colorhexa.com/">colorhexa</a> to help with color choices, hex values, etc. For this demo, the below settings are changed:</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span># by default, background is same as normal text
</span><span># change it to a shade of gray to easily distinguish code and text
</span><span>"background-color": "#f8f8f8",
</span><span>
</span><span># change italic to false, messes up comments with slashes
</span><span># change comment text-color to yet another shade of gray
</span><span>"Comment": {
</span><span> "text-color": "#9c9c9c",
</span><span> "background-color": null,
</span><span> "bold": false,
</span><span> "italic": false,
</span><span> "underline": false
</span><span>},
</span></code></pre>
<p><strong>Inline code</strong></p>
<p>Similar to changing background color for code snippets, I found a <a href="https://stackoverflow.com/q/40975004/4082052">solution online</a> for <em>inline</em> code snippets as well.</p>
<pre data-lang="latex" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-latex "><code class="language-latex" data-lang="latex"><span style="color:#72ab00;">\usepackage</span><span>{</span><span style="color:#a2a001;">fancyvrb</span><span>,newverbs,xcolor}
</span><span>
</span><span style="color:#b39f04;">\definecolor</span><span>{Light}{HTML}{F4F4F4}
</span><span>
</span><span style="color:#b39f04;">\let\oldtexttt\texttt
</span><span style="color:#668f14;">\renewcommand</span><span>{</span><span style="color:#c23f31;">\texttt</span><span>}[1]{
</span><span> </span><span style="color:#b39f04;">\colorbox</span><span>{Light}{</span><span style="color:#b39f04;">\oldtexttt</span><span>{#1}}
</span><span>}
</span></code></pre>
<p>Add <code>--highlight-style pygments.theme</code> and <code>--include-in-header inline_code.tex</code> to the script and generate the <code>pdf</code> again.</p>
<p>With <code>pandoc sample_2.md -f gfm -o sample_2.pdf</code> the output would be:</p>
<p><img src="/images/pandoc_pdf/default_syn.png" alt="Default syntax highlighting" /></p>
<p>With <code>./md2pdf_syn.sh sample_2.md sample_2_syn.pdf</code> the output is:</p>
<p><img src="/images/pandoc_pdf/customized_syn.png" alt="Customized syntax highlighting" /></p>
<br>
<p>For my <a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> book, by chance I found that using <code>ruby</code> instead of <code>python</code> for REPL code snippets syntax highlighting was better. Snapshot from <code>./md2pdf_syn.sh sample_3.md sample_3.pdf</code> result is shown below. For <code>python</code> directive, string output gets treated as a comment and color for boolean values isn't easy to distinguish from string values. The <code>ruby</code> directive treats string value as expected and boolean values are easier to spot.</p>
<p><img src="/images/pandoc_pdf/python_vs_ruby_syn.png" alt="REPL syntax highlighting" /></p>
<br>
<h2 id="bullet-styling">Bullet styling<a class="zola-anchor" href="#bullet-styling" aria-label="Anchor link for: bullet-styling">🔗</a></h2>
<p>This <a href="https://stackoverflow.com/q/22156999/4082052">stackoverflow Q&A</a> helped for bullet styling.</p>
<pre data-lang="latex" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-latex "><code class="language-latex" data-lang="latex"><span style="color:#72ab00;">\usepackage</span><span>{</span><span style="color:#a2a001;">enumitem</span><span>}
</span><span style="color:#72ab00;">\usepackage</span><span>{</span><span style="color:#a2a001;">amsfonts</span><span>}
</span><span>
</span><span style="color:#7f8989;">% level one
</span><span style="color:#b39f04;">\setlist</span><span>[itemize,1]{label=</span><span style="color:#d07711;">$</span><span style="color:#b39f04;">\bullet</span><span style="color:#d07711;">$</span><span>}
</span><span style="color:#7f8989;">% level two
</span><span style="color:#b39f04;">\setlist</span><span>[itemize,2]{label=</span><span style="color:#d07711;">$</span><span style="color:#b39f04;">\circ</span><span style="color:#d07711;">$</span><span>}
</span><span style="color:#7f8989;">% level three
</span><span style="color:#b39f04;">\setlist</span><span>[itemize,3]{label=</span><span style="color:#d07711;">$</span><span style="color:#b39f04;">\star</span><span style="color:#d07711;">$</span><span>}
</span></code></pre>
<p>Comparing <code>pandoc sample_4.md -f gfm -o sample_4.pdf</code> vs <code>./md2pdf_syn_bullet.sh sample_4.md sample_4_bullet.pdf</code> gives:</p>
<p><img src="/images/pandoc_pdf/bullet_styling.png" alt="Bullet styling" /></p>
<br>
<h2 id="pdf-properties">PDF properties<a class="zola-anchor" href="#pdf-properties" aria-label="Anchor link for: pdf-properties">🔗</a></h2>
<p>This <a href="https://tex.stackexchange.com/questions/23235/eliminate-edit-pdf-properties-added-by-pdflatex">tex.stackexchange Q&A</a> helped to change metadata. See also <a href="https://pspdfkit.com/blog/2018/whats-hiding-in-your-pdf/">pspdfkit: What’s Hiding in Your PDF?</a> and <a href="https://news.ycombinator.com/item?id=18381515">discussion on HN</a>.</p>
<pre data-lang="latex" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-latex "><code class="language-latex" data-lang="latex"><span style="color:#72ab00;">\usepackage</span><span>{</span><span style="color:#a2a001;">hyperref</span><span>}
</span><span>
</span><span style="color:#b39f04;">\hypersetup</span><span>{
</span><span> pdftitle={My awesome book},
</span><span> pdfauthor={learnbyexample},
</span><span> pdfsubject={pandoc},
</span><span> pdfkeywords={pandoc,pdf,xelatex}
</span><span>}
</span></code></pre>
<p><code>./md2pdf_syn_bullet_prop.sh sample_4.md sample_4_bullet_prop.pdf</code> gives:</p>
<p align="center"><img src="/images/pandoc_pdf/pdf_properties.png" alt="pdf properties" loading="lazy" /></p>
<br>
<h2 id="adding-table-of-contents">Adding table of contents<a class="zola-anchor" href="#adding-table-of-contents" aria-label="Anchor link for: adding-table-of-contents">🔗</a></h2>
<p>There's a handy option <code>--toc</code> to automatically include table of contents at top of the generated <code>pdf</code>. You can control number of levels using <code>--toc-depth</code> option, the default is 3 levels. You can also change the default string <code>Contents</code> to something else using the <code>-V toc-title</code> option.</p>
<p><code>./md2pdf_syn_bullet_prop_toc.sh sample_1.md sample_1_toc.pdf</code> gives:</p>
<p><img src="/images/pandoc_pdf/table_of_contents.png" alt="table of contents" /></p>
<br>
<h2 id="adding-cover-image">Adding cover image<a class="zola-anchor" href="#adding-cover-image" aria-label="Anchor link for: adding-cover-image">🔗</a></h2>
<p>To add something prior to table of contents, cover image for example, you can use a <code>tex</code> file and include it verbatim. Create a <code>tex</code> file (named as <code>cover.tex</code> here) with content as shown below:</p>
<pre data-lang="tex" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-tex "><code class="language-tex" data-lang="tex"><span style="color:#b39f04;">\includegraphics</span><span>{cover.png}
</span><span style="color:#b39f04;">\thispagestyle</span><span>{empty}
</span></code></pre>
<p>Then, modify the previous script <code>md2pdf_syn_bullet_prop_toc.sh</code> by adding <code>--include-before-body cover.tex</code> and tada — you get the cover image before table of contents. <code>\thispagestyle{empty}</code> helps to avoid page number on the cover page, see also <a href="https://tex.stackexchange.com/questions/360739/what-is-the-use-of-clearpage-thispagestyleempty-cleardoublepage">tex.stackexchange: clear page</a>.</p>
<p>The <code>bash</code> script invocation is now <code>./md2pdf_syn_bullet_prop_toc_cover.sh sample_5.md sample_5.pdf</code>.</p>
<p><img src="/images/warning.svg" alt="warning" /> You'll need at least one image in input markdown file, otherwise settings won't apply to the cover image and you may end up with a weird output. <code>sample_5.md</code> used in the command above includes an image. And be careful to use escapes if the image path can contain <code>tex</code> metacharacters.</p>
<br>
<h2 id="stylish-blockquote">Stylish blockquote<a class="zola-anchor" href="#stylish-blockquote" aria-label="Anchor link for: stylish-blockquote">🔗</a></h2>
<p>By default, blockquotes (lines starting with <code>></code> in markdown) are just indented in the <code>pdf</code> output. To make them standout, <a href="https://tex.stackexchange.com/questions/154528/how-to-change-the-background-color-and-border-of-a-pandoc-generated-blockquote">tex.stackexchange: change the background color and border of blockquote</a> helped.</p>
<p>Create <code>quote.tex</code> with the contents as shown below. You can change the colors to suit your own preferred style.</p>
<pre data-lang="tex" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-tex "><code class="language-tex" data-lang="tex"><span style="color:#72ab00;">\usepackage</span><span>{</span><span style="color:#a2a001;">tcolorbox</span><span>}
</span><span style="color:#b39f04;">\newtcolorbox</span><span>{myquote}{colback=red!5!white, colframe=red!75!black}
</span><span style="color:#b39f04;">\renewenvironment</span><span>{quote}{</span><span style="color:#72ab00;">\begin</span><span>{</span><span style="color:#5597d6;">myquote</span><span>}}{</span><span style="color:#72ab00;">\end</span><span>{</span><span style="color:#5597d6;">myquote</span><span>}}
</span></code></pre>
<p>The <code>bash</code> script invocation is now <code>./md2pdf_syn_bullet_prop_toc_cover_quote.sh sample_5.md sample_5_quote.pdf</code>. The difference between default and styled blockquote is shown below.</p>
<p align="center"><img src="/images/pandoc_pdf/styled_blockquote.png" alt="styling blockquotes" loading="lazy" /></p>
<br>
<h2 id="customizing-epub">Customizing epub<a class="zola-anchor" href="#customizing-epub" aria-label="Anchor link for: customizing-epub">🔗</a></h2>
<p>For a long time, I thought <code>epub</code> didn't make sense for programming books. Turned out, I wasn't using the right ebook readers. <strong>FBReader</strong> was good for novels but not ebooks with code snippets. When I used <a href="https://github.com/mate-desktop/atril">atril</a>, <a href="https://github.com/johnfactotum/foliate">foliate</a> or <a href="https://calibre-ebook.com/">calibre ebook-viewer</a>, the results were good.</p>
<p>I didn't know how to use <code>css</code> before trying to generate the <code>epub</code> version. Somehow, I managed to take the default <a href="https://github.com/jgm/pandoc/blob/master/data/epub.css">epub.css</a> provided by <code>pandoc</code> and customize it as close as possible to the <code>pdf</code> version. The modified <code>epub.css</code> is available from the <a href="https://github.com/learnbyexample/learnbyexample.github.io/tree/master/files/pandoc_pdf">learnbyexample.github.io repo</a>. The <code>bash</code> script to generate the <code>epub</code> is shown below and invoked as <code>./md2epub.sh sample_5.md sample_5.epub</code>. Note that <code>pygments.theme</code> is same as the <code>pdf</code> customization discussed before.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;">#!/bin/bash
</span><span>
</span><span style="color:#5597d6;">pandoc </span><span style="color:#d07711;">"$</span><span style="color:#acb3c2;">1</span><span style="color:#d07711;">" </span><span>\
</span><span style="color:#5597d6;"> -f</span><span> gfm \
</span><span style="color:#5597d6;"> --toc </span><span>\
</span><span style="color:#5597d6;"> --standalone </span><span>\
</span><span style="color:#5597d6;"> --top-level-division</span><span style="color:#72ab00;">=</span><span>chapter \
</span><span style="color:#5597d6;"> --highlight-style</span><span> pygments.theme \
</span><span style="color:#5597d6;"> --css</span><span> epub.css \
</span><span style="color:#5597d6;"> --metadata</span><span style="color:#72ab00;">=</span><span>title:</span><span style="color:#d07711;">"My awesome book" </span><span>\
</span><span style="color:#5597d6;"> --metadata</span><span style="color:#72ab00;">=</span><span>author:</span><span style="color:#d07711;">"learnbyexample" </span><span>\
</span><span style="color:#5597d6;"> --metadata</span><span style="color:#72ab00;">=</span><span>lang:</span><span style="color:#d07711;">"en-US" </span><span>\
</span><span style="color:#5597d6;"> --metadata</span><span style="color:#72ab00;">=</span><span>cover-image:</span><span style="color:#d07711;">"cover.png" </span><span>\
</span><span style="color:#5597d6;"> -o </span><span style="color:#d07711;">"$</span><span style="color:#acb3c2;">2</span><span style="color:#d07711;">"
</span></code></pre>
<br>
<h2 id="resource-links">Resource links<a class="zola-anchor" href="#resource-links" aria-label="Anchor link for: resource-links">🔗</a></h2>
<ul>
<li><a href="https://pandoc.org/MANUAL.html">pandoc: manual</a></li>
<li><a href="https://pandoc.org/demos.html">pandoc: demos</a></li>
<li><a href="https://github.com/jgm/pandoc/wiki/Pandoc-Tricks">pandoc: tips and tricks</a></li>
</ul>
<p><strong>More options and workflows for generating ebooks</strong>:</p>
<ul>
<li><a href="https://github.com/Wandmalfarbe/pandoc-latex-template">pandoc-latex-template</a> — a clean pandoc LaTeX template to convert your markdown files to PDF or LaTeX</li>
<li><a href="https://keleshev.com/writing-a-book-with-pandoc-make-and-vim/">Writing a book with pandoc, make, and vim</a></li>
<li><a href="https://quarto.org/">Quarto</a> — open source scientific and technical publishing system built on Pandoc</li>
<li><a href="https://github.com/iamgio/quarkdown">quarkdown</a> — a modern Markdown-based typetting system</li>
<li><a href="https://github.com/typst/typst">typst</a> — a new markup-based typesetting system that is powerful and easy to learn</li>
<li><a href="https://jupyterbook.org/en/stable/intro.html">Jupyter Book</a> — open source project for building beautiful, publication-quality books and documents from computational material
<ul>
<li>See also <a href="https://github.com/fastai/fastdoc">fastdoc</a> — the output of fastdoc is an asciidoc file for each input notebook. You can then use asciidoctor to convert that to HTML, DocBook, epub, mobi, and so forth</li>
</ul>
</li>
<li><a href="https://www.thedigitalcatbooks.com/maubook-introduction/">Mau</a> — template-based markup language, heavily inspired by AsciiDoc</li>
<li><a href="https://asciidoctor.org/docs/what-is-asciidoc/">Asciidoctor</a>
<ul>
<li><a href="https://github.com/daneah/asciidoc-book-template">Asciidoc book template</a></li>
<li><a href="https://shape-of-code.coding-guidelines.com/2019/08/11/my-books-pdf-generation-workflow/">pdf generation workflow with Asciidoc</a></li>
</ul>
</li>
<li><a href="https://www.sphinx-doc.org/en/master/index.html">Sphinx</a>
<ul>
<li><a href="https://digitalsuperpowers.com/blog/2019-02-16-publishing-ebook.html">Self-publishing a book with reStructuredText, Sphinx, Calibre, and vim</a></li>
</ul>
</li>
<li><a href="https://bookdown.org/home/">Bookdown</a></li>
<li><a href="https://orgmode.org/">Emacs orgmode</a></li>
<li><a href="https://casual-effects.com/markdeep/">Markdeep</a></li>
</ul>
<p><strong>Miscellaneous</strong></p>
<ul>
<li><a href="https://nickjanetakis.com/blog/vim-is-saving-me-hours-of-work-when-writing-books-and-courses">Vim is saving me hours of work when writing books & courses</a></li>
<li><a href="https://joecmarshall.com/posts/book-writing-environment/">Writing a Book with Unix</a></li>
<li><a href="https://askubuntu.com/questions/3697/how-do-i-install-fonts">askubuntu: How do I install fonts?</a></li>
<li><a href="https://tex.stackexchange.com/questions/9533/what-best-combination-of-fonts-for-serif-sans-and-mono-do-you-recommend">tex.stackexchange: What best combination of fonts for Serif, Sans, and Mono do you recommend?</a></li>
<li><a href="https://tug.org/FontCatalogue/">LaTeX font catalogue</a></li>
<li><a href="https://github.com/karthik/markdown_science/wiki/Tools-to-support-your-markdown-authoring">Tools to support markdown authoring</a></li>
<li><a href="https://picular.co/">picular: search engine for colors</a> and <a href="https://www.colorhexa.com/">colorhexa</a></li>
<li><a href="https://ebooks.stackexchange.com/questions?sort=votes">ebooks.stackexchange</a></li>
</ul>
Everything you need to know about sed substitution2025-05-07T00:00:00+00:002025-05-07T00:00:00+00:00https://learnbyexample.github.io/everything-about-sed-substitution/<p>The command name <code>sed</code> is derived from <strong>s</strong>tream <strong>ed</strong>itor. The most commonly used editing command is <strong>substitution</strong>, for which various examples are shown in this blog post.</p>
<blockquote>
<p><img src="/images/warning.svg" alt="warning" /> The examples presented here have been tested with <code>GNU sed</code>. Syntax and features might differ for other implementations.</p>
</blockquote>
<span id="continue-reading"></span><br>
<h2 id="basic-substitution">Basic Substitution<a class="zola-anchor" href="#basic-substitution" aria-label="Anchor link for: basic-substitution">🔗</a></h2>
<p>The substitute command syntax is <code>s/REGEXP/REPLACEMENT/FLAGS</code> where:</p>
<ul>
<li><code>s</code> stands for the <strong>substitute</strong> command</li>
<li><code>/</code> is an idiomatic delimiter character to separate various portions of the command</li>
<li><code>REGEXP</code> is the <strong>regular expression</strong> that defines the search portion</li>
<li><code>REPLACEMENT</code> refers to the replacement string</li>
<li><code>FLAGS</code> are options to change the default behavior of the command</li>
</ul>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># for each input line, change only the first ',' to '-'
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'1,2,3,4\na,b,c,d\n' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/,/-/'
</span><span style="color:#b3933a;">1</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">2</span><span>,</span><span style="color:#b3933a;">3</span><span>,</span><span style="color:#b3933a;">4
</span><span>a</span><span style="color:#72ab00;">-</span><span>b,c,d
</span><span>
</span><span style="color:#7f8989;"># you can change all the matches by adding the 'g' flag
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'1,2,3,4\na,b,c,d\n' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/,/=-=/g'
</span><span style="color:#b3933a;">1</span><span style="color:#72ab00;">=-=</span><span style="color:#b3933a;">2</span><span style="color:#72ab00;">=-=</span><span style="color:#b3933a;">3</span><span style="color:#72ab00;">=-=</span><span style="color:#b3933a;">4
</span><span>a=</span><span style="color:#72ab00;">-=</span><span>b=</span><span style="color:#72ab00;">-=</span><span>c=</span><span style="color:#72ab00;">-=</span><span>d
</span></code></pre>
<br>
<h2 id="filter-and-substitute">Filter and Substitute<a class="zola-anchor" href="#filter-and-substitute" aria-label="Anchor link for: filter-and-substitute">🔗</a></h2>
<p>You can use line numbers, regular expressions or a combination of them to select lines and then apply a command to these filtered lines. See the <a href="https://learnbyexample.github.io/learn_gnused/selective-editing.html">Selective editing</a> chapter from my ebook to learn more about the various kinds of addressing.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># make changes only to the first line
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'1,2,3,4\na,b,c,d\n' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'1 s/,/::/g'
</span><span style="color:#b3933a;">1</span><span>::</span><span style="color:#b3933a;">2</span><span>::</span><span style="color:#b3933a;">3</span><span>::</span><span style="color:#b3933a;">4
</span><span>a,b,c,d
</span><span>
</span><span style="color:#7f8989;"># apply substitution only if the input line does NOT contain '2'
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'1,2,3,4\na,b,c,d\n' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'/2/! s/,/-/g'
</span><span style="color:#b3933a;">1</span><span>,</span><span style="color:#b3933a;">2</span><span>,</span><span style="color:#b3933a;">3</span><span>,</span><span style="color:#b3933a;">4
</span><span>a</span><span style="color:#72ab00;">-</span><span>b</span><span style="color:#72ab00;">-</span><span>c</span><span style="color:#72ab00;">-</span><span>d
</span></code></pre>
<br>
<h2 id="regular-expressions">Regular Expressions<a class="zola-anchor" href="#regular-expressions" aria-label="Anchor link for: regular-expressions">🔗</a></h2>
<p>Only a handful of examples are shown here. See <a href="https://learnbyexample.github.io/learn_gnused/breere-regular-expressions.html">this chapter</a> from my ebook for a more detailed discussion. See <a href="https://learnbyexample.github.io/gnu-bre-ere-cheatsheet/">my blog post</a> for a cheatsheet.</p>
<p>Anchors:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># lines starting with 'par'
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'car par\nparty\nspare\n' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/^par/tas/'
</span><span>car par
</span><span>tasty
</span><span>spare
</span><span>
</span><span style="color:#7f8989;"># words starting with 'par'
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'car par\nparty\nspare\n' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/\bpar/1234/'
</span><span>car </span><span style="color:#b3933a;">1234
</span><span>1234ty
</span><span>spare
</span></code></pre>
<p>Alternation and Grouping:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># same as: sed -E 's/part|parrot|parent/X/g'
</span><span style="color:#7f8989;"># -E option enables ERE (default is BRE)
</span><span>$ echo </span><span style="color:#d07711;">'par part parrot parent' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/par(en|ro)?t/X/g'
</span><span>par </span><span style="color:#5597d6;">X X X
</span></code></pre>
<p>Character Class and Quantifiers:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># numbers >= 100 with optional leading zeros
</span><span>$ echo </span><span style="color:#d07711;">'0501 035 154 12 26 98234' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/\b0*[1-9][0-9]{2,}\b/X/g'
</span><span style="color:#5597d6;">X </span><span style="color:#b3933a;">035 </span><span style="color:#5597d6;">X </span><span style="color:#b3933a;">12 26 </span><span style="color:#5597d6;">X
</span><span>
</span><span style="color:#7f8989;"># retain only punctuation characters
</span><span>$ echo </span><span style="color:#d07711;">',pie tie#ink-eat_42' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/[^[:punct:]]+//g'
</span><span>,</span><span style="color:#7f8989;">#-_
</span></code></pre>
<p>Backreferences:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># remove two or more duplicate words separated by spaces
</span><span style="color:#7f8989;"># \b prevents false matches like 'the theatre', 'sand and stone' etc
</span><span>$ echo </span><span style="color:#d07711;">'aa a a a 42 f_1 f_1 f_13.14' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/\b(\w+)( \1)+\b/\1/g'
</span><span>aa a </span><span style="color:#b3933a;">42</span><span> f_1 f_13.</span><span style="color:#b3933a;">14
</span><span>
</span><span style="color:#7f8989;"># whole words that have at least one consecutive repeated character
</span><span>$ echo </span><span style="color:#d07711;">'effort flee facade oddball rat tool' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/\w*(\w)\1\w*/X/g'
</span><span style="color:#5597d6;">X X</span><span> facade </span><span style="color:#5597d6;">X</span><span> rat </span><span style="color:#5597d6;">X
</span><span>
</span><span style="color:#7f8989;"># match lowercase followed by underscore followed by lowercase
</span><span style="color:#7f8989;"># delete the underscore and convert the 2nd lowercase to uppercase
</span><span>$ echo </span><span style="color:#d07711;">'_fig aug_price next_line' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/([a-z])_([a-z])/\1\u\2/g'
</span><span>_fig augPrice nextLine
</span></code></pre>
<br>
<h2 id="replace-specific-occurrences">Replace Specific Occurrences<a class="zola-anchor" href="#replace-specific-occurrences" aria-label="Anchor link for: replace-specific-occurrences">🔗</a></h2>
<p>You can use a <em>number</em> as a flag to replace only that particular occurrence of the search term. If you combine this with the <code>g</code> flag, all occurrences after that particular match will also be replaced.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'apple:banana:cherry:fig:mango'
</span><span>
</span><span style="color:#7f8989;"># replace only the second occurrence
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/[^:]+/"&"/2'
</span><span style="color:#b3933a;">apple:</span><span style="color:#d07711;">"banana"</span><span style="color:#b3933a;">:cherry:fig:mango
</span><span>
</span><span style="color:#7f8989;"># replace all matches except the first occurrence
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/:/---/2g'
</span><span style="color:#b3933a;">apple:</span><span>banana</span><span style="color:#72ab00;">---</span><span>cherry</span><span style="color:#72ab00;">---</span><span>fig</span><span style="color:#72ab00;">---</span><span>mango
</span></code></pre>
<p>With the help of capture groups and backreferences, you can replace a specific occurrence from the end of the input line.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'car,art,pot,map,urn,ray,ear'
</span><span>
</span><span style="color:#7f8989;"># replace the last occurrence
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/(.*),/\1[]/'
</span><span>car,art,pot,map,urn,ray[]ear
</span><span>
</span><span style="color:#7f8989;"># generic version, where {N} refers to last but Nth occurrence
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/(.*),((.*,){3})/\1[]\2/'
</span><span>car,art,pot[]map,urn,ray,ear
</span></code></pre>
<br>
<h2 id="executing-external-commands">Executing External Commands<a class="zola-anchor" href="#executing-external-commands" aria-label="Anchor link for: executing-external-commands">🔗</a></h2>
<p>The <code>e</code> flag helps to insert the output of a shell command within <code>sed</code>.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># replace the entire line with the output of a shell command
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'apple\nreplace this line\n' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/^replace.*/date/e'
</span><span>apple
</span><span style="color:#5597d6;">Wednesday </span><span style="color:#b3933a;">07 </span><span style="color:#5597d6;">May </span><span style="color:#b3933a;">2025 10</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">25</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">34 </span><span style="color:#5597d6;">AM IST
</span><span>
</span><span style="color:#7f8989;"># after substitution, the command that gets executed is 'seq 3'
</span><span>$ echo </span><span style="color:#d07711;">'xyz 3' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/xyz/seq/e'
</span><span style="color:#b3933a;">1
</span><span style="color:#b3933a;">2
</span><span style="color:#b3933a;">3
</span></code></pre>
<br>
<h2 id="different-delimiters">Different Delimiters<a class="zola-anchor" href="#different-delimiters" aria-label="Anchor link for: different-delimiters">🔗</a></h2>
<p>The <code>/</code> character is idiomatically used as the REGEXP delimiter. But any character other than <code>\</code> and the newline character can be used instead.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># instead of this
</span><span>$ echo </span><span style="color:#d07711;">'/home/learnbyexample/reports' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/\/home\/learnbyexample\//~\//'
</span><span style="color:#72ab00;">~/</span><span>reports
</span><span>
</span><span style="color:#7f8989;"># use a different delimiter
</span><span>$ echo </span><span style="color:#d07711;">'/home/learnbyexample/reports' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s#/home/learnbyexample/#~/#'
</span><span style="color:#72ab00;">~/</span><span>reports
</span></code></pre>
<br>
<h2 id="in-place-editing">In-place Editing<a class="zola-anchor" href="#in-place-editing" aria-label="Anchor link for: in-place-editing">🔗</a></h2>
<p>The <code>-i</code> option is helpful to write back the changes to the original files itself. If you don't provide an argument to this option, backup of the original file won't be created.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat colors.txt
</span><span>deep blue
</span><span>light orange
</span><span>blue delight
</span><span>
</span><span style="color:#7f8989;"># no output on terminal as the -i option is used
</span><span>$ sed </span><span style="color:#72ab00;">-</span><span>i.bkp </span><span style="color:#d07711;">'s/blue/green/'</span><span> colors.txt
</span><span style="color:#7f8989;"># output from sed is written back to 'colors.txt'
</span><span>$ cat colors.txt
</span><span>deep green
</span><span>light orange
</span><span>green delight
</span><span>
</span><span style="color:#7f8989;"># original file is preserved in 'colors.txt.bkp'
</span><span>$ cat colors.txt.bkp
</span><span>deep blue
</span><span>light orange
</span><span>blue delight
</span></code></pre>
<p><code>*</code> in the argument to the <code>-i</code> option will be replaced with the input filename. So, <code>-i'bkp.*'</code> for <code>f1.txt</code> will create <code>bkp.f1.txt</code> as the backup. And if you use <code>old/*</code>, the backups will be under the same name but under the directory <code>old</code> (provided that directory already exists).</p>
<br>
<h2 id="manipulating-newlines">Manipulating Newlines<a class="zola-anchor" href="#manipulating-newlines" aria-label="Anchor link for: manipulating-newlines">🔗</a></h2>
<p>By default, <code>sed</code> reads the input line by line (with <code>\n</code> considered as the line ending). The newline character, if present, is removed and then added back when the pattern space is printed. Which implies that you cannot directly manipulate the newline character, unless you use features that results in more than one line in the pattern space.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># append the next line to the pattern space
</span><span style="color:#7f8989;"># and then replace newline character with a colon character
</span><span>$ seq </span><span style="color:#b3933a;">7 </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'N; s/\n/:/'
</span><span style="color:#b3933a;">1</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">2
</span><span style="color:#b3933a;">3</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">4
</span><span style="color:#b3933a;">5</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">6
</span><span style="color:#b3933a;">7
</span><span>
</span><span style="color:#7f8989;"># if line contains 'at', the next line gets appended to the pattern space
</span><span style="color:#7f8989;"># then the substitution is performed on the two lines in the buffer
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'gates\nnot\nused\n' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'/at/{N; s/s\nnot/d/}'
</span><span>gated
</span><span>used
</span></code></pre>
<br>
<h2 id="slurping-input">Slurping Input<a class="zola-anchor" href="#slurping-input" aria-label="Anchor link for: slurping-input">🔗</a></h2>
<p>If the input doesn't have NUL characters, then the <code>-z</code> option is handy to process the entire input as a single string. This is effective only for files small enough to fit the available machine memory. It would also depend on the regular expression, as some patterns have exponential relationship with respect to the data size.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># add ; to the previous line if the current line starts with c
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'cater\ndog\ncoat\ncutter\nmat\n' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span>z </span><span style="color:#d07711;">'s/\nc/;&/g'
</span><span>cater
</span><span>dog;
</span><span>coat;
</span><span>cutter
</span><span>mat
</span></code></pre>
<br>
<h2 id="fixed-string-substitution">Fixed String Substitution<a class="zola-anchor" href="#fixed-string-substitution" aria-label="Anchor link for: fixed-string-substitution">🔗</a></h2>
<p>Typically, you'd need to escape <code>\</code>, <code>&</code> and the delimiter for the string used in the replacement section. For the search section, the characters to be escaped will depend upon whether you are using BRE or ERE.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># replacement string
</span><span>$ r=</span><span style="color:#d07711;">'a/b&c\d'
</span><span>$ r=$(</span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%s</span><span style="color:#d07711;">' "$r" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s#[\&/]#</span><span style="color:#aeb52b;">\\</span><span style="color:#d07711;">&#g'</span><span>)
</span><span>
</span><span style="color:#7f8989;"># ERE version for the search string
</span><span>$ s=</span><span style="color:#d07711;">'{[(\ta^b/d).*+?^$|]}'
</span><span>$ s=$(</span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%s</span><span style="color:#d07711;">' "$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s#[{[()^$*?+.\|/]#</span><span style="color:#aeb52b;">\\</span><span style="color:#d07711;">&#g'</span><span>)
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%s</span><span style="color:#d07711;">\n' 'f*{[(\ta^b/d).*+?^$|]} - 3' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/'"$s"'/'"$r"'/g'
</span><span>f</span><span style="color:#72ab00;">*</span><span>a</span><span style="color:#72ab00;">/</span><span>b</span><span style="color:#72ab00;">&</span><span>c\d </span><span style="color:#72ab00;">- </span><span style="color:#b3933a;">3
</span><span>
</span><span style="color:#7f8989;"># BRE version for the search string
</span><span>$ s=</span><span style="color:#d07711;">'{[(\ta^b/d).*+?^$|]}'
</span><span>$ s=$(</span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%s</span><span style="color:#d07711;">' "$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s#[[^$*.\/]#</span><span style="color:#aeb52b;">\\</span><span style="color:#d07711;">&#g'</span><span>)
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%s</span><span style="color:#d07711;">\n' 'f*{[(\ta^b/d).*+?^$|]} - 3' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/'"$s"'/'"$r"'/g'
</span><span>f</span><span style="color:#72ab00;">*</span><span>a</span><span style="color:#72ab00;">/</span><span>b</span><span style="color:#72ab00;">&</span><span>c\d </span><span style="color:#72ab00;">- </span><span style="color:#b3933a;">3
</span></code></pre>
<p>See <a href="https://learnbyexample.github.io/multiline-search-and-replace/">my blog post</a> for multiline fixed string substitution examples.</p>
<br>
<h2 id="programming-ebooks">Programming ebooks<a class="zola-anchor" href="#programming-ebooks" aria-label="Anchor link for: programming-ebooks">🔗</a></h2>
<p>Check out <a href="https://learnbyexample.github.io/books/">my ebooks</a> on Regular Expressions, Linux CLI tools, Python and Vim. You can get them all as a single bundle via <a href="https://leanpub.com/b/learnbyexample-all-books">leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/all-books">gumroad</a>.</p>
CLI text processing with GNU awk book announcement2025-03-26T00:00:00+00:002025-03-26T00:00:00+00:00https://learnbyexample.github.io/cli-text-processing-awk-announcement/<p>Hello!</p>
<p>I am pleased to announce a new version of my <strong>CLI text processing with GNU awk</strong> ebook.</p>
<p>Learn the <code>GNU awk</code> command step-by-step from beginner to advanced levels with <strong>hundreds of examples and exercises</strong>. This book will dive deep into field processing, show examples for filtering features, multiple file processing, how to construct solutions that depend on multiple records, how to compare records and fields between two or more files, how to identify duplicates while maintaining input order and so on. <strong>Regular expressions</strong> will also be discussed in detail.</p>
<span id="continue-reading"></span><br>
<h2 id="release-offers">Release offers<a class="zola-anchor" href="#release-offers" aria-label="Anchor link for: release-offers">🔗</a></h2>
<p>To celebrate the new release, you can download the PDF/EPUB versions of <strong>CLI text processing with GNU awk</strong> for FREE till 06-April-2025. You can still pay if you wish ;)</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/gnu_awk">Gumroad</a></li>
<li><a href="https://leanpub.com/gnu_awk/c/free">Leanpub</a></li>
</ul>
<p>Here are some more amazing offers:</p>
<ul>
<li><strong>All 13 books bundle</strong> is $18 (normal price $36) — <a href="https://leanpub.com/b/learnbyexample-all-books/c/half_price">Leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/all-books/HalfPrice">Gumroad</a></li>
<li><strong>Linux CLI Text Processing</strong> is $10 (normal price $20) — <a href="https://leanpub.com/b/linux-cli-text-processing/c/half_price">Leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/linux-cli-text-processing/HalfPrice">Gumroad</a></li>
</ul>
<br>
<h2 id="what-s-new">What's new?<a class="zola-anchor" href="#what-s-new" aria-label="Anchor link for: what-s-new">🔗</a></h2>
<ul>
<li>Command version updated to <strong>GNU awk 5.3.1</strong></li>
<li>Added details for the <code>--csv</code> option and the <code>\u</code> escape sequence</li>
<li>Corrected typos, updated exercises, descriptions and external links</li>
<li>Updated Acknowledgements section</li>
</ul>
<br>
<h2 id="videos">Videos<a class="zola-anchor" href="#videos" aria-label="Anchor link for: videos">🔗</a></h2>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/KIa_EaYwGDI" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<p>Check out my <a href="https://learnbyexample.github.io/tips/">programming tips</a> covering Python, command line tools and Vim:</p>
<ul>
<li><a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F">Python tips</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY">Linux command line tips</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4NN2tK-59ZiNBm-o64-Yvos">Vim tips</a></li>
</ul>
<br>
<h2 id="interactive-tui-app">Interactive TUI app<a class="zola-anchor" href="#interactive-tui-app" aria-label="Anchor link for: interactive-tui-app">🔗</a></h2>
<p>I also wrote an <a href="https://github.com/learnbyexample/TUI-apps/blob/main/AwkExercises">interactive TUI app</a> based on some of the exercises from the ebook. Reference solutions are also provided.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/AwkExercises/awk_exercises.png" alt="Sample screenshot from the interactive TUI app for awk exercises" loading="lazy" /></p>
<br>
<h2 id="table-of-contents">Table of Contents<a class="zola-anchor" href="#table-of-contents" aria-label="Anchor link for: table-of-contents">🔗</a></h2>
<ol>
<li>Preface</li>
<li>Installation and Documentation</li>
<li>awk introduction</li>
<li>Regular Expressions</li>
<li>Field separators</li>
<li>Record separators</li>
<li>In-place file editing</li>
<li>Using shell variables</li>
<li>Control Structures</li>
<li>Built-in functions</li>
<li>Multiple file input</li>
<li>Processing multiple records</li>
<li>Two file processing</li>
<li>Dealing with duplicates</li>
<li>awk scripts</li>
<li>Gotchas and Tips</li>
<li>Further Reading</li>
</ol>
<br>
<h2 id="web-version">Web version<a class="zola-anchor" href="#web-version" aria-label="Anchor link for: web-version">🔗</a></h2>
<p>You can read the book online here: <a href="https://learnbyexample.github.io/learn_gnuawk/">https://learnbyexample.github.io/learn_gnuawk/</a></p>
<br>
<h2 id="github-repo">GitHub repo<a class="zola-anchor" href="#github-repo" aria-label="Anchor link for: github-repo">🔗</a></h2>
<p>Visit <a href="https://github.com/learnbyexample/learn_gnuawk">https://github.com/learnbyexample/learn_gnuawk</a> for markdown source, example files, exercise solutions, sample chapters and other details related to the book.</p>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://learnbyexample.github.io/customizing-pandoc/">my blog post</a> on how to customize <code>pandoc</code> for generating beautiful PDF/EPUB versions from GitHub style markdown.</p>
</blockquote>
<br>
<h2 id="newsletter">Newsletter<a class="zola-anchor" href="#newsletter" aria-label="Anchor link for: newsletter">🔗</a></h2>
<p>Subscribe to <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> — free newsletter covering programming resources, updates on what I am creating, tools, free ebooks and more, delivered every Friday.</p>
<br>
<h2 id="feedback-and-errata">Feedback and Errata<a class="zola-anchor" href="#feedback-and-errata" aria-label="Anchor link for: feedback-and-errata">🔗</a></h2>
<p>I would highly appreciate it if you'd <strong>let me know how you felt about this book</strong>. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.</p>
<p>You can reach me via:</p>
<ul>
<li>Issue Manager: <a href="https://github.com/learnbyexample/learn_gnuawk/issues">https://github.com/learnbyexample/learn_gnuawk/issues</a></li>
<li>E-mail: <code>echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode</code></li>
<li>Twitter: <a href="https://twitter.com/learn_byexample">https://twitter.com/learn_byexample</a></li>
</ul>
<p>Happy learning :)</p>
awk idioms explained2025-02-18T00:00:00+00:002025-03-28T00:00:00+00:00https://learnbyexample.github.io/awk-idioms-explained/<p>Do you find <code>awk</code> one-liners cryptic? Stuff like <code>!a[$0]++</code>, <code>1</code>, <code>$1=$1</code>, <code>NR==FNR</code> and <code>-v RS=</code>? You'll find examples and brief explanations for such idioms in this post.</p>
<blockquote>
<p><img src="/images/warning.svg" alt="warning" /> The examples presented here have been tested with <code>GNU awk</code>. These are likely to work with most other implementations of <code>awk</code> as well.</p>
</blockquote>
<span id="continue-reading"></span><br>
<h2 id="awk-command-structure">awk command structure<a class="zola-anchor" href="#awk-command-structure" aria-label="Anchor link for: awk-command-structure">🔗</a></h2>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>awk 'cond1{action1} cond2{action2} ... condN{actionN}'
</span></code></pre>
<p>When a conditional expression isn't provided, the action is always executed. When an action isn't provided, the <code>$0</code> variable (which has the contents of the current record being processed) is printed if the conditional expression evaluates to true.</p>
<br>
<h2 id="regexp-filtering">Regexp filtering<a class="zola-anchor" href="#regexp-filtering" aria-label="Anchor link for: regexp-filtering">🔗</a></h2>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># same as: grep 'at' and sed -n '/at/p'
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'gate\napple\nwhat\nkite\n' </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#d07711;">'/at/'
</span><span>gate
</span><span>what
</span><span>
</span><span style="color:#7f8989;"># same as: grep -v 'e' and sed -n '/e/!p'
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'gate\napple\nwhat\nkite\n' </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#d07711;">'!/e/'
</span><span>what
</span></code></pre>
<p>The generic syntax is <code>string ~ /regexp/</code> to check if the given string matches the regexp and <code>string !~ /regexp/</code> to invert the condition.</p>
<ul>
<li><code>/regexp/</code> is a shortcut for <code>$0 ~ /regexp/{print $0}</code></li>
<li><code>!/regexp/</code> is a shortcut for <code>$0 !~ /regexp/{print $0}</code></li>
</ul>
<br>
<h2 id="idiomatic-use-of-1">Idiomatic use of 1<a class="zola-anchor" href="#idiomatic-use-of-1" aria-label="Anchor link for: idiomatic-use-of-1">🔗</a></h2>
<p>Non-zero numeric values and non-empty strings are <em>truthy</em> (zero and empty strings are <em>falsy</em>). Idiomatically, <code>1</code> is used as a conditional expression to print the contents of <code>$0</code>.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ echo </span><span style="color:#d07711;">'ring amazing jar' </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#d07711;">'{sub(/ing/, "ed", $2)} 1'
</span><span>ring amazed jar
</span><span>
</span><span>$ seq </span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#d07711;">'BEGIN{print "---"} 1; END{print "==="}'
</span><span style="color:#72ab00;">---
</span><span style="color:#b3933a;">1
</span><span style="color:#b3933a;">2
</span><span style="color:#72ab00;">===
</span></code></pre>
<br>
<h2 id="special-variables">Special variables<a class="zola-anchor" href="#special-variables" aria-label="Anchor link for: special-variables">🔗</a></h2>
<ul>
<li><code>$0</code> contains the current record being processed</li>
<li><code>$1</code> first field</li>
<li><code>$2</code> second field and so on</li>
<li><code>FS</code> input field separator</li>
<li><code>OFS</code> output field separator</li>
<li><code>NF</code> number of fields</li>
<li><code>RS</code> input record separator</li>
<li><code>ORS</code> output record separator</li>
<li><code>NR</code> number of records (i.e. line number) for the entire input</li>
<li><code>FNR</code> number of records per file</li>
</ul>
<br>
<h2 id="removing-duplicates">Removing duplicates<a class="zola-anchor" href="#removing-duplicates" aria-label="Anchor link for: removing-duplicates">🔗</a></h2>
<p><code>awk '!a[$0]++'</code> is one of the most famous <code>awk</code> one-liners. It eliminates line based duplicates while retaining the input order.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat purchases.txt
</span><span>coffee
</span><span>tea
</span><span>washing powder
</span><span>coffee
</span><span>tea
</span><span>coffee milkshake
</span><span>soap
</span><span>tea
</span><span>washing soda
</span><span>
</span><span>$ awk </span><span style="color:#d07711;">'{print +a[$0] "\t" $0; a[$0]++}'</span><span> purchases.txt
</span><span style="color:#b3933a;">0</span><span> coffee
</span><span style="color:#b3933a;">0</span><span> tea
</span><span style="color:#b3933a;">0</span><span> washing powder
</span><span style="color:#b3933a;">1</span><span> coffee
</span><span style="color:#b3933a;">1</span><span> tea
</span><span style="color:#b3933a;">0</span><span> coffee milkshake
</span><span style="color:#b3933a;">0</span><span> soap
</span><span style="color:#b3933a;">2</span><span> tea
</span><span style="color:#b3933a;">0</span><span> washing soda
</span><span>
</span><span style="color:#7f8989;"># only the entries with zero in the first column will be retained
</span><span>$ awk </span><span style="color:#d07711;">'!a[$0]++'</span><span> purchases.txt
</span><span>coffee
</span><span>tea
</span><span>washing powder
</span><span>coffee milkshake
</span><span>soap
</span><span>washing soda
</span></code></pre>
<p><code>a[$0]</code> creates an uninitialized element in array <code>a</code> with <code>$0</code> as the key (if the key doesn't exist yet). Thus, <code>!a[$0]</code> will succeed only on the first occurrence of an item (since an uninitialized value is <em>falsy</em>) and the post-increment operator will ensure that further instances of an item will fail the conditional expression.</p>
<br>
<h2 id="rebuild-0">Rebuild $0<a class="zola-anchor" href="#rebuild-0" aria-label="Anchor link for: rebuild-0">🔗</a></h2>
<p>Sometimes you just want to change the field separator, or perform some record-level text processing and then print it with a new field separator. In such cases, you'll have to explicitly fake a field operation — otherwise the field separation update won't happen for <code>$0</code>.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'sample123string42with777numbers'
</span><span>
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">F</span><span style="color:#d07711;">'[0-9]+' </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">OFS</span><span style="color:#72ab00;">=</span><span>, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span>sample,string,with,numbers
</span><span>
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">F</span><span style="color:#d07711;">'[0-9]+' </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">OFS</span><span style="color:#72ab00;">=- </span><span style="color:#d07711;">'{gsub(/[aeiou]/, ""); $1=$1} 1'
</span><span>smpl</span><span style="color:#72ab00;">-</span><span>strng</span><span style="color:#72ab00;">-</span><span>wth</span><span style="color:#72ab00;">-</span><span>nmbrs
</span></code></pre>
<br>
<h2 id="paragraph-mode">Paragraph mode<a class="zola-anchor" href="#paragraph-mode" aria-label="Anchor link for: paragraph-mode">🔗</a></h2>
<p>When <code>RS</code> is set to an empty string, one or more consecutive empty lines is used as the input record separator.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat para.txt
</span><span>hello world
</span><span>
</span><span>hi there
</span><span>how are you
</span><span>
</span><span>just doing
</span><span>believe it
</span><span>
</span><span>banana
</span><span>papaya
</span><span>mango
</span><span>
</span><span>much ado about nothing
</span><span>he he he
</span><span>adios amigo
</span><span>
</span><span style="color:#7f8989;"># uninitialized variable 's' will be empty for the first match
</span><span style="color:#7f8989;"># afterwards, 's' will provide the empty line separation
</span><span>$ awk </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">RS</span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'/do/{print s $0; s="\n"}'</span><span> para.txt
</span><span>just doing
</span><span>believe it
</span><span>
</span><span>much ado about nothing
</span><span>he he he
</span><span>adios amigo
</span></code></pre>
<br>
<h2 id="two-file-processing">Two file processing<a class="zola-anchor" href="#two-file-processing" aria-label="Anchor link for: two-file-processing">🔗</a></h2>
<p>For two files as input, <code>NR==FNR</code> will be true only when the first file is being processed. The <code>next</code> statement will skip the rest of the code for the current record. </p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat marks.txt
</span><span>dept </span><span style="color:#b39f04;">name</span><span> marks
</span><span>ece raj </span><span style="color:#b3933a;">53
</span><span>ece joel </span><span style="color:#b3933a;">72
</span><span>eee moi </span><span style="color:#b3933a;">68
</span><span>cse surya </span><span style="color:#b3933a;">81
</span><span>eee tia </span><span style="color:#b3933a;">59
</span><span>ece om </span><span style="color:#b3933a;">92
</span><span>cse amy </span><span style="color:#b3933a;">67
</span><span>
</span><span>$ cat dept_mark.txt
</span><span>ece </span><span style="color:#b3933a;">70
</span><span>eee </span><span style="color:#b3933a;">65
</span><span>cse </span><span style="color:#b3933a;">80
</span><span>
</span><span style="color:#7f8989;"># match dept and minimum marks specified in dept_mark.txt
</span><span>$ awk </span><span style="color:#d07711;">'NR==FNR{d[$1]=$2; next}
</span><span style="color:#d07711;"> $1 in d && $3 >= d[$1]'</span><span> dept_mark.txt marks.txt
</span><span>ece joel </span><span style="color:#b3933a;">72
</span><span>eee moi </span><span style="color:#b3933a;">68
</span><span>cse surya </span><span style="color:#b3933a;">81
</span><span>ece om </span><span style="color:#b3933a;">92
</span></code></pre>
<p><img src="/images/warning.svg" alt="warning" /> Note that the <code>NR==FNR</code> logic will fail if the first file is empty, since <code>NR</code> wouldn't get a chance to increment. You can set a flag after the first file has been processed to avoid this issue — for example, <code>awk '!f{a[$0]; next} !($0 in a)' file1 f=1 file2</code>. See <a href="https://unix.stackexchange.com/a/237110/109046">this unix.stackexchange thread</a> for more workarounds.</p>
<br>
<h2 id="forcing-string-and-numeric-context">Forcing string and numeric context<a class="zola-anchor" href="#forcing-string-and-numeric-context" aria-label="Anchor link for: forcing-string-and-numeric-context">🔗</a></h2>
<p>Strings are automatically converted to a number when used in an arithmetic expression (for example, <code>"42" + 5</code>). You can use the unary <code>+</code> and <code>-</code> operators to force numeric context. If the string doesn't start with a valid number (ignoring any starting whitespaces), it will be treated as <code>0</code>.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ seq </span><span style="color:#b3933a;">3 </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#d07711;">'{sum += $0} END{print sum}'
</span><span style="color:#b3933a;">6
</span><span>$ awk </span><span style="color:#d07711;">'{sum += $0} END{print sum}' </span><span style="color:#72ab00;">/</span><span>dev</span><span style="color:#72ab00;">/</span><span>null
</span><span>
</span><span>$ awk </span><span style="color:#d07711;">'{sum += $0} END{print +sum}' </span><span style="color:#72ab00;">/</span><span>dev</span><span style="color:#72ab00;">/</span><span>null
</span><span style="color:#b3933a;">0
</span></code></pre>
<p>Similarly, you can concatenate a string to a number to force string context.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ awk </span><span style="color:#d07711;">'BEGIN{n1="5.0"; n2=5; if(n1==n2) print "equal"}'
</span><span>$ awk </span><span style="color:#d07711;">'BEGIN{n1="5.0"; n2=5; if(n1==n2".0") print "equal"}'
</span><span>equal
</span></code></pre>
<p>See <a href="https://www.gnu.org/software/gawk/manual/gawk.html#Strings-And-Numbers">gawk manual: How awk Converts Between Strings and Numbers</a> for more details.</p>
<br>
<h2 id="programming-ebooks">Programming ebooks<a class="zola-anchor" href="#programming-ebooks" aria-label="Anchor link for: programming-ebooks">🔗</a></h2>
<p>Check out <a href="https://learnbyexample.github.io/books/">my ebooks</a> on Regular Expressions, Linux CLI tools, Python and Vim. You can get them all as a single bundle via <a href="https://leanpub.com/b/learnbyexample-all-books">leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/all-books">gumroad</a>.</p>
OS installation woes2025-02-12T00:00:00+00:002025-03-04T00:00:00+00:00https://learnbyexample.github.io/mini/os-installation-woes/<p>I prefer stability and thus I let my Linux LTS distributions to last almost till the entire end of support time. Having learned from previous transition periods, I started maintaining notes on what softwares I install/purge and some of the customization stuff. This has vastly reduced the pain of a fresh OS installation, but the fact remains that there'll always be some persistent and annoying trouble.</p>
<p>The one I encountered this time around is really perplexing and I'm afraid of trying to figure out the root cause. For now, I'm happy with the workaround I ended up with.</p>
<p>I use <code>redshift</code> to set the color temperature of computer display. It is a simple temperature setting that doesn't depend on time or place, so my config is simple. However, it wasn't working when I moved from good old Ubuntu to Linux Mint (because Ubuntu is no longer user friendly). As per <a href="https://forums.linuxmint.com/viewtopic.php?t=422300">this discussion on Linux Mint forum</a>, <code>geoclue2</code> no longer works. That shouldn't matter for me since I don't need location services. Whatever, that same thread mentioned <a href="https://github.com/faf0/sct">xsct</a> as a simpler alternative that exactly fits my need (with a bonus of changing screen brightness!).</p>
<p>I installed it and a really really simple command from the terminal was all it needed to work. So, what was the annoying issue? I couldn't get it to <a href="https://wiki.archlinux.org/title/Xfce#Autostart">autostart</a> on login no matter what I did! At first, I thought perhaps I needed to use the full path of the command, but that obviously didn't solve my troubles. After fruitless search on the internet, I almost thought to ask a question on the forum. Before that though, I had the bright idea (really basic debugging rule) to first check if my autostart setup was working at all. I chose to autostart a terminal on login via the <code>xfce4-terminal</code> command and it did work!</p>
<p>So, why wasn't <code>xsct</code> working? No idea. But reading the <code>man</code> page of the terminal emulator showed that I can choose to execute a command with the <code>-e</code> option. So, that was my inelegant workaround! The desktop entry is shown below if you are curious. If you know why <code>Exec=xsct 3500 0.9</code> doesn't work compared to <code>Exec=xfce4-terminal -e 'xsct 3500 0.9'</code>, do let me know!</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>[Desktop Entry]
</span><span>Encoding=UTF-8
</span><span>Version=0.9.4
</span><span>Type=Application
</span><span>Name=displaytemperature
</span><span>Comment=
</span><span>Exec=xfce4-terminal -e 'xsct 3500 0.9'
</span><span>OnlyShowIn=XFCE;
</span><span>RunHook=0
</span><span>StartupNotify=false
</span><span>Terminal=false
</span><span>Hidden=false
</span></code></pre>
<p><strong>Update:</strong></p>
<p>The above solution didn't work as I hoped. The screen setting would activate some of the time but most often it didn't. I resorted to creating a keyboard shortcut, which I'd use when the activation failed.</p>
<p>The terminal would always show up briefly on login though, which meant the issue wasn't due to the autostart failing altogether. One day I happened to notice that the screen setting would actually take effect before immediately relapsing to the normal temperature. And thus I arrived at the current solution that hasn't failed yet. I wrote a shell script that first used the <code>sleep</code> command to create a delay of one second before calling <code>xsct</code>. The autostart desktop entry now calls this script — again, using <code>xfce4-terminal -e</code> because for some reason calling the script directly still fails 🤷.</p>
Understanding Python re(gex)? book announcement2025-01-22T00:00:00+00:002025-01-22T00:00:00+00:00https://learnbyexample.github.io/understanding-python-regex-announcement/<p>Hello!</p>
<p>I just published a new version of my <strong>Understanding Python re(gex)?</strong> ebook.</p>
<p>This book will help you learn Python Regular Expressions step-by-step from beginner to advanced levels with <strong>hundreds of examples and exercises</strong>. The standard library <code>re</code> as well as the third-party <code>regex</code> module are covered in this book.</p>
<span id="continue-reading"></span><br>
<h2 id="release-offers">Release offers<a class="zola-anchor" href="#release-offers" aria-label="Anchor link for: release-offers">🔗</a></h2>
<p>To celebrate the new release, you can download the PDF/EPUB versions of <strong>Understanding Python re(gex)?</strong> for FREE till 31-Jan-2025.</p>
<ul>
<li><a href="https://leanpub.com/py_regex/c/free">Leanpub</a></li>
<li><a href="https://learnbyexample.gumroad.com/l/py_regex/free">Gumroad</a></li>
</ul>
<p>Here are some more amazing offers:</p>
<ul>
<li><strong>All 13 books bundle</strong> is $18 (normal price $36) — <a href="https://leanpub.com/b/learnbyexample-all-books/c/half_price">Leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/all-books/HalfPrice">Gumroad</a></li>
<li><strong>100 Page Python Intro</strong> is FREE (normal price $10) — <a href="https://leanpub.com/100pagepythonintro/c/free">Leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/100pagepythonintro/free">Gumroad</a></li>
</ul>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://learnbyexample.github.io/customizing-pandoc/">my blog post</a> on how to customize <code>pandoc</code> for generating beautiful PDF/EPUB versions from GitHub style markdown.</p>
</blockquote>
<br>
<h2 id="what-s-new">What's new?<a class="zola-anchor" href="#what-s-new" aria-label="Anchor link for: what-s-new">🔗</a></h2>
<ul>
<li>Python version updated to 3.13
<ul>
<li>deprecated features, SyntaxWarning, name change from <code>re.error</code> to <code>re.PatternError</code></li>
</ul>
</li>
<li>Corrected typos, updated descriptions, timing results and external links</li>
<li>Exercises are now numbered instead of using alphabets</li>
</ul>
<br>
<h2 id="videos">Videos<a class="zola-anchor" href="#videos" aria-label="Anchor link for: videos">🔗</a></h2>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/2x2n7ynamm8" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<p>Check out my <a href="https://learnbyexample.github.io/tips/">programming tips</a> covering Python, command line tools and Vim:</p>
<ul>
<li><a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F">Python tips</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY">Linux command line tips</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4NN2tK-59ZiNBm-o64-Yvos">Vim tips</a></li>
</ul>
<br>
<h2 id="re-gex-playground">re(gex)? playground<a class="zola-anchor" href="#re-gex-playground" aria-label="Anchor link for: re-gex-playground">🔗</a></h2>
<p>To make it easier to experiment, I wrote on an interactive app. See <a href="https://github.com/learnbyexample/TUI-apps/tree/main/PyRegexPlayground">PyRegexPlayground</a> repo for installation instructions and usage guide. A sample screenshot is shown below:</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PyRegexPlayground/pyregex_finditer.png" alt="Sample screenshot from the Playground screen" loading="lazy" /></p>
<br>
<h2 id="re-gex-exercises">re(gex)? exercises<a class="zola-anchor" href="#re-gex-exercises" aria-label="Anchor link for: re-gex-exercises">🔗</a></h2>
<p>I wrote another TUI app to help you solve exercises from this book interactively. See <a href="https://github.com/learnbyexample/TUI-apps/tree/main/PyRegexExercises">PyRegexExercises</a> repo for installation steps and <a href="https://github.com/learnbyexample/TUI-apps/blob/main/PyRegexExercises/app_guide.md">app_guide.md</a> for instructions on using this app. Here's a sample screenshot:</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PyRegexExercises/pyregex_exercises.png" alt="Sample screenshot for Python regex exercises" loading="lazy" /></p>
<p>See my blog post <a href="https://learnbyexample.github.io/python-regex-cheatsheet/">Python regex cheatsheet</a> for a quick reference.</p>
<br>
<h2 id="table-of-contents">Table of Contents<a class="zola-anchor" href="#table-of-contents" aria-label="Anchor link for: table-of-contents">🔗</a></h2>
<ol>
<li>Preface</li>
<li>Why is it needed?</li>
<li>re introduction</li>
<li>Anchors</li>
<li>Alternation and Grouping</li>
<li>Escaping metacharacters</li>
<li>Dot metacharacter and Quantifiers</li>
<li>Interlude: Tools for debugging and visualization</li>
<li>Working with matched portions</li>
<li>Character class</li>
<li>Groupings and backreferences</li>
<li>Interlude: Common tasks</li>
<li>Lookarounds</li>
<li>Flags</li>
<li>Unicode</li>
<li>regex module</li>
<li>Gotchas</li>
<li>Further Reading</li>
</ol>
<br>
<h2 id="web-version">Web version<a class="zola-anchor" href="#web-version" aria-label="Anchor link for: web-version">🔗</a></h2>
<p>You can read the book online here: <a href="https://learnbyexample.github.io/py_regular_expressions/">https://learnbyexample.github.io/py_regular_expressions/</a></p>
<br>
<h2 id="github-repo">GitHub repo<a class="zola-anchor" href="#github-repo" aria-label="Anchor link for: github-repo">🔗</a></h2>
<p>Visit <a href="https://github.com/learnbyexample/py_regular_expressions">https://github.com/learnbyexample/py_regular_expressions</a> for markdown source, example files, exercise solutions, sample chapters and other details related to the book.</p>
<br>
<h2 id="newsletter">Newsletter<a class="zola-anchor" href="#newsletter" aria-label="Anchor link for: newsletter">🔗</a></h2>
<p>Subscribe to <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> — free newsletter covering programming resources, updates on what I am creating, tools, free ebooks and more, delivered every Friday.</p>
<br>
<h2 id="feedback-and-errata">Feedback and Errata<a class="zola-anchor" href="#feedback-and-errata" aria-label="Anchor link for: feedback-and-errata">🔗</a></h2>
<p>I would highly appreciate it if you'd <strong>let me know how you felt about this book</strong>. It could be anything from a simple thank you, rating/review, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.</p>
<p>You can reach me via:</p>
<ul>
<li>Issue Manager: <a href="https://github.com/learnbyexample/py_regular_expressions/issues">https://github.com/learnbyexample/py_regular_expressions/issues</a></li>
<li>E-mail: <code>learn by [email protected]</code> (without the spaces)</li>
<li>Twitter: <a href="https://twitter.com/learn_byexample">https://twitter.com/learn_byexample</a></li>
</ul>
<p>Happy learning :)</p>
Coloring matched portions with GNU grep, sed and awk2025-01-13T00:00:00+00:002025-09-05T00:00:00+00:00https://learnbyexample.github.io/coloring-matched-portions-grep-sed-awk/<p>You might already know how to use the <code>--color</code> option to highlight matched portions with <code>GNU grep</code>. In this post, you'll see how to use ANSI escape sequences to format matched portions with <code>GNU sed</code> and <code>GNU awk</code>.</p>
<span id="continue-reading"></span><br>
<h2 id="gnu-grep">GNU grep<a class="zola-anchor" href="#gnu-grep" aria-label="Anchor link for: gnu-grep">🔗</a></h2>
<p>Consider this sample input file:</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ cat fruits.txt
</span><span>banana mango cherry pineapple
</span><span>grape fig apple dragonfruit papaya
</span><span>watermelon cashew tomato orange
</span><span>almond lime grapefruit walnut
</span></code></pre>
<p>The output for <code>grep --color -wE '[ago]\w+' fruits.txt</code> is shown below:</p>
<p align="center"><img src="/images/grep_color.png" alt="Example for displaying matched portions in color with GNU grep" /></p>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://learnbyexample.github.io/learn_gnugrep_ripgrep/frequently-used-options.html#colored-output">this section</a> from my ebook on <code>GNU grep</code> for more details about this option. <a href="https://github.com/BurntSushi/ripgrep">ripgrep</a> has a more featured support for color formatting, see <a href="https://learnbyexample.github.io/learn_gnugrep_ripgrep/ripgrep.html#colored-output">this section</a> for an example.</p>
<br>
<h2 id="formatting-with-ansi-escape-sequences">Formatting with ANSI escape sequences<a class="zola-anchor" href="#formatting-with-ansi-escape-sequences" aria-label="Anchor link for: formatting-with-ansi-escape-sequences">🔗</a></h2>
<p>Here are some examples to show how you can format text in the terminal using ANSI escape sequences:</p>
<p align="center"><img src="/images/ANSI_escape_sequences_formatting.png" alt="Examples for formatting with ANSI escape sequences" /></p>
<p>Your choice of formatting goes between <code>\033[</code> and <code>m</code>. You can use <code>01</code> for <strong>bold</strong>, <code>03</code> for <em>italics</em> and <code>04</code> for <u>underline</u>. <code>31</code> is for the color <font color='red'>red</font>, <code>32</code> for <font color='green'>green</font> and <code>34</code> for <font color='blue'>blue</font>. Multiple formats can be specified by separating the parameters with a semicolon. Using <code>0</code> turns off the format (otherwise, it will persist in the current terminal session until turned off).</p>
<p>If you find a file that has accidentally saved such escape sequences, you can use <code>cat -v</code> to identify them.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ echo 'one (two) three' | grep --color=always '(two)' | cat -v
</span><span>one ^[[01;31m^[[K(two)^[[m^[[K three
</span></code></pre>
<p>See also:</p>
<ul>
<li><a href="https://stackoverflow.com/q/4842424/4082052">List of ANSI color escape sequences</a></li>
<li><a href="https://unix.stackexchange.com/q/148/109046">Colorizing your terminal and shell environment</a></li>
<li><a href="https://wizardzines.com/comics/colours/">A comic on terminal colors</a></li>
</ul>
<p><img src="/images/info.svg" alt="info" /> Note that you can also use <code>\e</code> instead of <code>\033</code> in the above examples. However, that will not work with the <code>GNU awk</code> examples shown below.</p>
<br>
<h2 id="gnu-sed">GNU sed<a class="zola-anchor" href="#gnu-sed" aria-label="Anchor link for: gnu-sed">🔗</a></h2>
<p>With <code>GNU sed</code>, you'll need to use <code>\o</code> to specify an octal escape sequence. Here's an example: </p>
<p align="center"><img src="/images/sed_color.png" alt="Example for displaying matched portions in color with GNU sed" /></p>
<p>Here's an example for processing lines bounded by distinct markers:</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ cat blocks.txt
</span><span>mango
</span><span>icecream
</span><span>--start 1--
</span><span>dragon 1234
</span><span>unicorn 6789
</span><span>**end 1**
</span><span>have a nice day
</span><span>--start 2--
</span><span>a b c
</span><span>apple banana cherry
</span><span>**end 2**
</span><span>par,far,mar,tar
</span></code></pre>
<p align="center"><img src="/images/sed_multiline_color.png" alt="Highlighting portion of interest in multiline processing with GNU sed" /></p>
<br>
<h2 id="gnu-awk">GNU awk<a class="zola-anchor" href="#gnu-awk" aria-label="Anchor link for: gnu-awk">🔗</a></h2>
<p>With <code>GNU awk</code>, you can embed the ANSI escape sequences in a string similar to the <code>printf</code> example seen earlier.</p>
<p align="center"><img src="/images/awk_color.png" alt="Example for displaying matched portions in color with GNU awk" /></p>
<p>Here's a field processing example:</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ cat marks.txt
</span><span>Dept Name Marks
</span><span>ECE Raj 53
</span><span>ECE Joel 62
</span><span>EEE Moi 68
</span><span>CSE Surya 81
</span><span>EEE Tia 59
</span><span>ECE Om 92
</span><span>CSE Amy 67
</span><span>
</span><span>$ cat filter.txt
</span><span>ECE 70
</span><span>EEE 65
</span><span>CSE 80
</span></code></pre>
<p align="center"><img src="/images/awk_color_field_processing.png" alt="Color field processing results with GNU awk" /></p>
<br>
<h2 id="linux-cli-ebooks">Linux CLI ebooks<a class="zola-anchor" href="#linux-cli-ebooks" aria-label="Anchor link for: linux-cli-ebooks">🔗</a></h2>
<p>Check out <a href="https://learnbyexample.github.io/books/">my ebooks</a> if you are interested in learning more about Linux CLI basics, coreutils, text processing tools like <code>GNU grep</code>, <code>GNU sed</code>, <code>GNU awk</code>, <code>perl</code> and more! You can get them all as a single bundle via <a href="https://leanpub.com/b/learnbyexample-all-books">leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/all-books">gumroad</a>.</p>
100 Page Python Intro book announcement2024-12-19T00:00:00+00:002024-12-24T00:00:00+00:00https://learnbyexample.github.io/100-page-python-intro-book-announcement/<p>Hello!</p>
<p>I am pleased to announce a new version of my <strong>100 Page Python Intro</strong> ebook. This book is a short, introductory guide for the Python programming language. This book is well suited:</p>
<ul>
<li>As a reference material for Python beginner workshops</li>
<li>If you have prior experience with another programming language</li>
<li>If you want a complement resource after reading a Python basics book, watching a video course, etc</li>
</ul>
<span id="continue-reading"></span><br>
<h2 id="release-offers">Release offers<a class="zola-anchor" href="#release-offers" aria-label="Anchor link for: release-offers">🔗</a></h2>
<p>To celebrate the new release, you can download PDF/EPUB versions of the ebook for FREE till 02-Jan-2025. You can still pay if you wish ;)</p>
<ul>
<li><a href="https://leanpub.com/100pagepythonintro/c/YearEndSale">https://leanpub.com/100pagepythonintro/c/YearEndSale</a></li>
<li><a href="https://learnbyexample.gumroad.com/l/100pagepythonintro">https://learnbyexample.gumroad.com/l/100pagepythonintro</a></li>
</ul>
<p>Two of my bundles are on sale as well:</p>
<ul>
<li><strong>All 13 Books Bundle</strong> — $15 (normal price $32), learn Regular Expressions, Linux CLI tools, Python, Vim and more!
<ul>
<li><a href="https://leanpub.com/b/learnbyexample-all-books/c/YearEndSale">Leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/all-books/YearEndSale">Gumroad</a></li>
</ul>
</li>
<li><strong>Awesome Regex Bundle</strong> — $10 (normal price $20), Python, Ruby, JavaScript, BRE/ERE, PCRE and Vim regular expressions
<ul>
<li><a href="https://leanpub.com/b/regex/c/YearEndSale">Leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/regex/YearEndSale">Gumroad</a></li>
</ul>
</li>
</ul>
<br>
<h2 id="what-s-new">What's new?<a class="zola-anchor" href="#what-s-new" aria-label="Anchor link for: what-s-new">🔗</a></h2>
<ul>
<li>Python version updated to <strong>3.13.0</strong></li>
<li>Added more exercises and you can now practice some of them using this <a href="https://github.com/learnbyexample/TUI-apps/tree/main/PythonExercises">interactive TUI app</a></li>
<li>Descriptions and external links were updated/corrected</li>
<li>Updated Acknowledgements section</li>
<li>Code snippets related to info/warning sections will now appear as a single block</li>
<li>New cover image</li>
<li>Images centered for EPUB format</li>
</ul>
<br>
<h2 id="videos">Videos<a class="zola-anchor" href="#videos" aria-label="Anchor link for: videos">🔗</a></h2>
<p align="center"><iframe width="560" height="315" src="https://www.youtube.com/embed/aoWJzaSs0cs" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<p>Check out my <a href="https://learnbyexample.github.io/tips/">programming tips</a> covering Python, command line tools and Vim:</p>
<ul>
<li><a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F">Python tips</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY">Linux command line tips</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4NN2tK-59ZiNBm-o64-Yvos">Vim tips</a></li>
</ul>
<br>
<h2 id="testimonials">Testimonials<a class="zola-anchor" href="#testimonials" aria-label="Anchor link for: testimonials">🔗</a></h2>
<blockquote>
<p>It's very thorough, written with care, and presented in a way that makes sense. Even as an intermediate Python programmer, I found use in this book.</p>
<p>— feedback by <a href="https://healeycodes.com/">Andrew Healey</a> on an early draft of "100 Page Python Intro" mentioned in <a href="https://news.ycombinator.com/item?id=26082464">this Hacker News thread</a></p>
</blockquote>
<br>
<h2 id="interactive-tui-app">Interactive TUI app<a class="zola-anchor" href="#interactive-tui-app" aria-label="Anchor link for: interactive-tui-app">🔗</a></h2>
<p>I also wrote an <a href="https://github.com/learnbyexample/TUI-apps/tree/main/PythonExercises">interactive TUI app</a> based on some of the exercises from the ebook. Reference solutions are also provided.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PythonExercises/python_exercises.png" alt="Sample screenshot from the interactive TUI app for Python exercises" loading="lazy" /></p>
<br>
<h2 id="table-of-contents">Table of Contents<a class="zola-anchor" href="#table-of-contents" aria-label="Anchor link for: table-of-contents">🔗</a></h2>
<ol>
<li>Preface</li>
<li>Introduction</li>
<li>Numeric data types</li>
<li>Strings and user input</li>
<li>Defining functions</li>
<li>Control structures</li>
<li>Importing and creating modules</li>
<li>Installing modules and Virtual environments</li>
<li>Exception handling</li>
<li>Debugging</li>
<li>Testing</li>
<li>Tuple and Sequence operations</li>
<li>List</li>
<li>Mutability</li>
<li>Dict</li>
<li>Set</li>
<li>Text processing</li>
<li>Comprehensions and Generator expressions</li>
<li>Dealing with files</li>
<li>Executing external commands</li>
<li>Command line arguments</li>
</ol>
<br>
<h2 id="web-version">Web version<a class="zola-anchor" href="#web-version" aria-label="Anchor link for: web-version">🔗</a></h2>
<p>You can also read the book online here: <a href="https://learnbyexample.github.io/100_page_python_intro/introduction.html">https://learnbyexample.github.io/100_page_python_intro/introduction.html</a>.</p>
<br>
<h2 id="github-repo">GitHub repo<a class="zola-anchor" href="#github-repo" aria-label="Anchor link for: github-repo">🔗</a></h2>
<p>Visit <a href="https://github.com/learnbyexample/100_page_python_intro">https://github.com/learnbyexample/100_page_python_intro</a> for programs, example files, markdown source and other details about the book.</p>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://learnbyexample.github.io/customizing-pandoc/">my blog post</a> on how to customize <code>pandoc</code> for generating beautiful PDF/EPUB versions from GitHub style markdown.</p>
</blockquote>
<br>
<h2 id="feedback">Feedback<a class="zola-anchor" href="#feedback" aria-label="Anchor link for: feedback">🔗</a></h2>
<p>I would highly appreciate it if you'd <strong>let me know how you felt about this book</strong>. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.</p>
<p>You can reach me via:</p>
<ul>
<li>Issue Manager: <a href="https://github.com/learnbyexample/100_page_python_intro/issues">https://github.com/learnbyexample/100_page_python_intro/issues</a></li>
<li>E-mail: <code>echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode</code></li>
<li>Twitter: <a href="https://twitter.com/learn_byexample">https://twitter.com/learn_byexample</a></li>
</ul>
<p>Happy learning :)</p>
Festive offers for books on Python, Linux, Regular Expressions, Vim and more!2024-11-12T00:00:00+00:002024-11-29T00:00:00+00:00https://learnbyexample.github.io/programming-deals-2024/<p>Hello!</p>
<p>Here are some awesome deals for programming books and courses during the 2024 festive season.</p>
<span id="continue-reading"></span><br>
<h2 id="my-ebooks">My ebooks<a class="zola-anchor" href="#my-ebooks" aria-label="Anchor link for: my-ebooks">🔗</a></h2>
<p>Offers valid till 02-Dec-2024. You can get them on Leanpub:</p>
<ul>
<li><a href="https://leanpub.com/b/learnbyexample-all-books/c/FestiveOffer">All 13 Books bundle</a> — $15 (normal price $32), learn Regular Expressions, Linux CLI tools, Python, Vim and more!</li>
<li><a href="https://leanpub.com/b/linux-cli-text-processing/c/FestiveOffer">Linux CLI Text Processing bundle</a> — $10 (normal price $20), grep, sed, awk, perl and ruby one-liners, GNU coreutils, CLI computing</li>
<li><a href="https://leanpub.com/b/python-bundle/c/FestiveOffer">Learn by example Python bundle</a> — $8 (normal price $15), Python introduction, Regular Expressions and Projects</li>
<li><a href="https://leanpub.com/py_regex/c/FestiveOffer">Understanding Python re(gex)?</a> — FREE (normal price $10)</li>
</ul>
<p>You can also avail these offers on Gumroad:</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer">All 13 Books Bundle</a> — $15 (normal price $32), learn Regular Expressions, Linux CLI tools, Python, Vim and more!</li>
<li><a href="https://learnbyexample.gumroad.com/l/linux-cli-text-processing/FestiveOffer">Linux CLI Text Processing bundle</a> — $10 (normal price $20), grep, sed, awk, perl and ruby one-liners, GNU coreutils, CLI computing</li>
<li><a href="https://learnbyexample.gumroad.com/l/python-bundle/FestiveOffer">Learn by example Python bundle</a> — $8 (normal price $15), Python introduction, Regular Expressions and Projects</li>
<li><a href="https://learnbyexample.gumroad.com/l/py_regex">Understanding Python re(gex)?</a> — FREE (normal price $10)</li>
</ul>
<p align="center"><a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer"><img src="/images/books/all_books_bundle.png" alt="All books bundle" loading="lazy" /></a></p>
<br>
<h2 id="indie-creators">Indie creators<a class="zola-anchor" href="#indie-creators" aria-label="Anchor link for: indie-creators">🔗</a></h2>
<ul>
<li><a href="https://mathspp.gumroad.com/l/all-books-bundle/BF24">7 Python books bundle</a> — 50% off</li>
<li><a href="https://driscollis.gumroad.com/">Python books</a> — 35% off with <code>BF24</code> discount code</li>
<li><a href="https://adamchainz.gumroad.com/">Ebooks on Django and Git</a> — 50% off, plus purchasing power parity if applicable
<ul>
<li>see also author's <a href="https://adamj.eu/tech/2024/11/18/django-black-friday-deals-2024/">blog post</a> for links to other Django-related deals</li>
</ul>
</li>
<li><a href="https://thepythoncodingplace.thinkific.com/order?ct=20aec16f-ab0e-467f-bc1b-7e98e64d47f4">The Python Coding Place Membership</a> — 40% off with <code>black2024</code> discount code</li>
<li><a href="https://www.pythonmorsels.com/courses/jumpstart/overview/">Python Jumpstart</a> — 50% launch discount
<ul>
<li>see also author's <a href="https://treyhunner.com/2024/11/python-black-friday-and-cyber-monday-sales-2024/">blog post</a> for links to other Python deals</li>
</ul>
</li>
<li><a href="https://wizardzines.com/">Wizard Zines</a> — 50% off with <code>WIZARDPDF</code> discount code</li>
<li><a href="https://shrutibalasa.gumroad.com/l/ebooks-combo/BLACKFRIDAY24">CSS Flex and Grid + Level up with Tailwind CSS</a> — 60% off</li>
<li><a href="https://leanpub.com/everydayrailsrspec/c/rubyconf2024">Everyday Rails Testing with RSpec</a> — 53% off</li>
</ul>
<h2 id="other-deals">Other deals<a class="zola-anchor" href="#other-deals" aria-label="Anchor link for: other-deals">🔗</a></h2>
<ul>
<li><a href="https://media.pragprog.com/newsletters/2024-11-20.html">The Pragmatic Bookshelf</a> — 40% off on all ebooks and audio books</li>
<li><a href="https://mailchi.mp/leanpub/weekly-sale-2024-nov-bf-5388297">Leanpub Black Friday Sale</a> — offers for programming books, bundles and courses</li>
<li><a href="https://github.com/trungdq88/Awesome-Black-Friday-Cyber-Monday">Huge list of awesome deals</a> — tools, productivity, books, courses, etc</li>
<li><a href="https://github.com/0x90n/InfoSec-Black-Friday">InfoSec Hack Friday</a> — InfoSec related software/tools</li>
<li><a href="https://blackfridaydeals.dev/">blackfridaydeals.dev</a> — Hottest Black Friday Deals for Developers</li>
</ul>
<br>
<p>Happy learning :)</p>
Interactive Python Exercises and Quiz2024-10-29T00:00:00+00:002024-11-14T00:00:00+00:00https://learnbyexample.github.io/interactive-python-exercises/<p>Having an interactive program that automatically loads questions and checks the solution is wonderful to have while learning a topic. This <a href="https://github.com/learnbyexample/TUI-apps/tree/main/PythonExercises">TUI app</a> has beginner to intermediate level exercises and multiple-choice questions for Python learners.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PythonExercises/python_exercises.png" alt="Sample screenshot for Python exercises" loading="lazy" /></p>
<span id="continue-reading"></span><br>
<h2 id="installation">Installation<a class="zola-anchor" href="#installation" aria-label="Anchor link for: installation">🔗</a></h2>
<p>This app is available on PyPI as <a href="https://pypi.org/project/pythonexercises/">pythonexercises</a>. Example installation instructions are shown below, adjust them based on your preferences and OS.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># virtual environment
</span><span style="color:#5597d6;">$</span><span> python3</span><span style="color:#5597d6;"> -m</span><span> venv textual_apps
</span><span style="color:#5597d6;">$</span><span> cd textual_apps
</span><span style="color:#5597d6;">$</span><span> source bin/activate
</span><span style="color:#5597d6;">$</span><span> pip install pythonexercises
</span><span>
</span><span style="color:#7f8989;"># launch the app
</span><span style="color:#5597d6;">$</span><span> pythonexercises
</span></code></pre>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> If you are on Windows, using the <a href="https://en.wikipedia.org/wiki/Windows_Terminal">Windows Terminal</a> is recommended. See <a href="https://github.com/learnbyexample/TUI-apps/issues/3#issuecomment-1481488042">this issue</a> for Virtual Environment commands and other details.</p>
</blockquote>
<p>To run the app without having to enter the virtual environment again, add this alias to <code>.bashrc</code> (or equivalent):</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># you'll have to change the path
</span><span style="color:#b39f04;">alias </span><span style="color:#c23f31;">pythonexercises</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'/path/to/textual_apps/bin/pythonexercises'
</span></code></pre>
<p>As an alternative to manually managing such virtual environments, you can use <a href="https://github.com/pypa/pipx">https://github.com/pypa/pipx</a> instead:</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> pipx install pythonexercises
</span><span style="color:#5597d6;">$</span><span> pythonexercises
</span></code></pre>
<p>As yet another alternative, you can install <code>textual</code> (see <a href="https://textual.textualize.io/getting_started/">Textual documentation</a> for more details), clone this repository and run the <code>python_exercises.py</code> file. You'll need to install <code>textual[syntax]</code> to enable syntax highlighting (see <a href="https://textual.textualize.io/widgets/text_area/#syntax-highlighting-dependencies">documentation</a> for more details).</p>
<p>Adjust the terminal dimensions for the widgets to appear properly, for example 84x25 (characters x lines). Here's another screenshot:</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PythonExercises/python_quiz.png" alt="Sample screenshot for Python quiz" loading="lazy" /></p>
<br>
<h2 id="guide">Guide<a class="zola-anchor" href="#guide" aria-label="Anchor link for: guide">🔗</a></h2>
<p>See <a href="https://github.com/learnbyexample/TUI-apps/blob/main/PythonExercises/app_guide.md">app_guide.md</a> for instructions.</p>
<br>
<h2 id="ebook">Ebook<a class="zola-anchor" href="#ebook" aria-label="Anchor link for: ebook">🔗</a></h2>
<p>The exercise and quiz questions in this app have been adapted from my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
<br>
<h2 id="feedback">Feedback<a class="zola-anchor" href="#feedback" aria-label="Anchor link for: feedback">🔗</a></h2>
<p>I'd highly appreciate your feedback. Please file <a href="https://github.com/learnbyexample/TUI-apps/issues">an issue</a> if there are bugs, crashes, etc.</p>
<p>Hope you find this TUI app useful. Happy learning :)</p>
Vim Reference Guide book announcement2024-08-20T00:00:00+00:002024-08-20T00:00:00+00:00https://learnbyexample.github.io/vim-reference-guide-announcement/<p>Hello!</p>
<p>I am pleased to announce a new version of my <strong>Vim Reference Guide</strong> ebook. This is intended as a concise learning resource for beginner to intermediate level Vim users. It has more in common with cheatsheets than a typical text book. Topics like Regular Expressions and Macros have more detailed explanations and examples due to their complexity. I hope this guide would make it much easier for you to discover Vim features and learning resources than my own blundering experience.</p>
<span id="continue-reading"></span><br>
<h2 id="release-offers">Release offers<a class="zola-anchor" href="#release-offers" aria-label="Anchor link for: release-offers">🔗</a></h2>
<p>To celebrate the new release, you can download PDF/EPUB versions of the ebook for FREE till 31-Aug-2024. You can still pay if you wish ;)</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/vim_reference_guide">https://learnbyexample.gumroad.com/l/vim_reference_guide</a></li>
<li><a href="https://leanpub.com/vim_reference_guide/c/new_vim_release">https://leanpub.com/vim_reference_guide/c/new_vim_release</a></li>
</ul>
<p>Two of my bundles are on sale as well:</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/all-books/new_vim_release">All Books Bundle</a> is $15 (normal price $32) — all my 13 programming ebooks</li>
<li><a href="https://learnbyexample.gumroad.com/l/linux-cli-text-processing/new_vim_release">Linux CLI Text Processing</a> is 50% OFF — grep, sed, awk, perl and ruby one-liners, coreutils, cli computing</li>
</ul>
<br>
<h2 id="what-s-new">What's new?<a class="zola-anchor" href="#what-s-new" aria-label="Anchor link for: what-s-new">🔗</a></h2>
<ul>
<li>Updated ebook for <strong>Vim version 9.1</strong></li>
<li>Corrected typos</li>
<li>Some of the examples, descriptions and external links were updated</li>
<li>New cover image</li>
</ul>
<br>
<h2 id="videos">Videos<a class="zola-anchor" href="#videos" aria-label="Anchor link for: videos">🔗</a></h2>
<p align="center"><iframe width="560" height="315" src="https://www.youtube.com/embed/4ybTvTr3SQc" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<p>Visit <a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4NN2tK-59ZiNBm-o64-Yvos">this playlist</a> for video demos on most of the topics from the ebook.</p>
<br>
<h2 id="testimonials">Testimonials<a class="zola-anchor" href="#testimonials" aria-label="Anchor link for: testimonials">🔗</a></h2>
<p>Got several suggestions and feedback when <a href="https://news.ycombinator.com/item?id=30684232">my submission about this book</a> reached the front page of Hacker News.</p>
<blockquote>
<p>Great job on this! — rendall</p>
<p>Hi, great work releasing this! Trying to explain vim concisely is always an interesting challenge and I had a great time reading your attempt in this book. I always find it really interesting on how people try to group certain vim functions in a way that makes sense to people that don't use vim. I think you cover that idea pretty well in your 'Vim philosophy and features' section whilst not making it overly abstract and keeping it relatable. — doix</p>
<p>Neat stuff! One piece of feedback is that I would include "+p and "+yy in the copy and paste section. — mrpotato</p>
<p>I learnt regular expression by reading your books, thank you for the great work. — LamJH</p>
</blockquote>
<p>A comment from another <a href="https://news.ycombinator.com/item?id=31931804">Hacker News thread</a>:</p>
<blockquote>
<p>I stumbled upon your vi post a few days ago, really like the style. Keep it up!</p>
</blockquote>
<br>
<h2 id="vim-prank">Vim prank<a class="zola-anchor" href="#vim-prank" aria-label="Anchor link for: vim-prank">🔗</a></h2>
<p>Did you know that Vim has an <em>easy</em> mode? It can be rather hard to use for those already familiar with Vim modes. I wrote a <a href="https://learnbyexample.github.io/mini/vim-prank/">blog post</a> about this mode, which was interesting enough to reach the front page of Hacker News!</p>
<br>
<h2 id="table-of-contents">Table of Contents<a class="zola-anchor" href="#table-of-contents" aria-label="Anchor link for: table-of-contents">🔗</a></h2>
<ol>
<li>Preface</li>
<li>Introduction</li>
<li>Insert mode</li>
<li>Normal mode</li>
<li>Command-line mode</li>
<li>Visual mode</li>
<li>Regular Expressions</li>
<li>Macro</li>
<li>Customizing Vim</li>
<li>CLI options</li>
</ol>
<br>
<h2 id="web-version">Web version<a class="zola-anchor" href="#web-version" aria-label="Anchor link for: web-version">🔗</a></h2>
<p>You can also read the book online here: <a href="https://learnbyexample.github.io/vim_reference/">https://learnbyexample.github.io/vim_reference/</a>.</p>
<br>
<h2 id="github-repo">GitHub repo<a class="zola-anchor" href="#github-repo" aria-label="Anchor link for: github-repo">🔗</a></h2>
<p>Visit <a href="https://github.com/learnbyexample/vim_reference">https://github.com/learnbyexample/vim_reference</a> for markdown source and other details related to the book.</p>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://learnbyexample.github.io/customizing-pandoc/">my blog post</a> on how to customize <code>pandoc</code> for generating beautiful PDF/EPUB versions from GitHub style markdown.</p>
</blockquote>
<br>
<h2 id="newsletter">Newsletter<a class="zola-anchor" href="#newsletter" aria-label="Anchor link for: newsletter">🔗</a></h2>
<p>Subscribe to <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> — free newsletter covering programming resources, updates on what I am creating, tools, free ebooks and more, delivered every Friday.</p>
<br>
<h2 id="feedback-and-errata">Feedback and Errata<a class="zola-anchor" href="#feedback-and-errata" aria-label="Anchor link for: feedback-and-errata">🔗</a></h2>
<p>I would highly appreciate it if you'd <strong>let me know how you felt about this book</strong>. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.</p>
<p>You can reach me via:</p>
<ul>
<li>Issue Manager: <a href="https://github.com/learnbyexample/vim_reference/issues">https://github.com/learnbyexample/vim_reference/issues</a></li>
<li>E-mail: <code>echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode</code></li>
<li>Twitter: <a href="https://twitter.com/learn_byexample">https://twitter.com/learn_byexample</a></li>
</ul>
<p>Happy learning :)</p>
Basic examples for the Linux date command2024-07-31T00:00:00+00:002025-01-28T00:00:00+00:00https://learnbyexample.github.io/mini/linux-date-command-examples/<p>I rarely ever use the <code>date</code> command, but when I need it I almost always struggle to get the right incantation. So, I'm just going to record such examples in this blog post (and some good to know features).</p>
<p>There'll also be learning resources linked at the end of the post.</p>
<br>
<h2 id="really-basic-examples">Really basic examples</h2>
<p>The <code>date</code> command by itself shows the current time. But that's rarely what I need, since I could just use the calendar widget at the bottom of my desktop screen. Perhaps useful to copy the string format and modify system time with the <code>-s</code> option?</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># use the -u option for UTC (Coordinated Universal Time)
</span><span>$ date
</span><span style="color:#5597d6;">Wednesday </span><span style="color:#b3933a;">31 </span><span style="color:#5597d6;">July </span><span style="color:#b3933a;">2024 03</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">53</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">01 </span><span style="color:#5597d6;">PM IST
</span></code></pre>
<p>Instead, I need particular parts in a particular format. For example, to represent the time component in a dynamically constructed filename as part of a shell script.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># same as: date +%F or date -I or date --iso-8601
</span><span>$ date </span><span style="color:#72ab00;">+%</span><span style="color:#5597d6;">Y</span><span style="color:#72ab00;">-%</span><span>m</span><span style="color:#72ab00;">-%</span><span>d
</span><span style="color:#b3933a;">2024</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">07</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">31
</span><span>
</span><span>$ date </span><span style="color:#72ab00;">+%</span><span style="color:#5597d6;">Y</span><span style="color:#c49a39;">/%m/</span><span style="color:#72ab00;">%</span><span>d
</span><span style="color:#b3933a;">2024</span><span style="color:#72ab00;">/</span><span style="color:#b3933a;">07</span><span style="color:#72ab00;">/</span><span style="color:#b3933a;">31
</span><span>$ date </span><span style="color:#72ab00;">+%</span><span>y</span><span style="color:#72ab00;">-%</span><span>m</span><span style="color:#72ab00;">-%</span><span>d
</span><span style="color:#b3933a;">24</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">07</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">31
</span><span>
</span><span style="color:#7f8989;"># use 'b' and 'B' for month names
</span><span>$ date </span><span style="color:#72ab00;">+%</span><span>a
</span><span style="color:#5597d6;">Wed
</span><span>$ date </span><span style="color:#72ab00;">+%</span><span style="color:#5597d6;">A
</span><span style="color:#5597d6;">Wednesday
</span></code></pre>
<p>You can use <code>%x</code> to get the locale representation:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ date </span><span style="color:#72ab00;">+</span><span style="color:#d07711;">%x
</span><span style="color:#d07711;">31/07/24
</span></code></pre>
<p>For hours, minutes and seconds:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># same as: date +%T
</span><span>$ date </span><span style="color:#72ab00;">+%</span><span style="color:#b3933a;">H:</span><span style="color:#72ab00;">%</span><span style="color:#b3933a;">M:</span><span style="color:#72ab00;">%</span><span style="color:#5597d6;">S
</span><span style="color:#b3933a;">16</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">00</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">32
</span><span>
</span><span style="color:#7f8989;"># same as: date +%Y-%m-%dT%H:%M:%S%:z
</span><span>$ date </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">Iseconds
</span><span style="color:#b3933a;">2024</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">07</span><span style="color:#72ab00;">-</span><span>31T16</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">09</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">27</span><span style="color:#72ab00;">+</span><span style="color:#b3933a;">05</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">30
</span></code></pre>
<br>
<h2 id="displaying-and-converting-epoch-seconds">Displaying and converting epoch seconds</h2>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># total seconds since the epoch (1970-01-01 00:00:00 UTC)
</span><span>$ date </span><span style="color:#72ab00;">+</span><span style="color:#d07711;">%s
</span><span style="color:#d07711;">1722422393
</span><span>
</span><span>$ date </span><span style="color:#72ab00;">-</span><span>d @</span><span style="color:#b3933a;">1722422393 </span><span style="color:#72ab00;">+</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%F %T</span><span style="color:#d07711;">'
</span><span style="color:#b3933a;">2024</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">07</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">31 16</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">09</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">53
</span></code></pre>
<p>You can also provide an input file for conversion using the <code>-f</code> option:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat epochs.txt
</span><span>@</span><span style="color:#b3933a;">0000000000
</span><span>@</span><span style="color:#b3933a;">1234567890
</span><span>@</span><span style="color:#b3933a;">2222222222
</span><span>
</span><span style="color:#7f8989;"># recall that the -u option gives you UTC
</span><span>$ date </span><span style="color:#72ab00;">-</span><span>u </span><span style="color:#72ab00;">-</span><span>f epochs.txt </span><span style="color:#72ab00;">+</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%F %T</span><span style="color:#d07711;">'
</span><span style="color:#b3933a;">1970</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">01</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">01 00</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">00</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">00
</span><span style="color:#b3933a;">2009</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">02</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">13 23</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">31</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">30
</span><span style="color:#b3933a;">2040</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">06</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">02 03</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">57</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">02
</span></code></pre>
<br>
<h2 id="date-arithmetic">Date arithmetic</h2>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ date </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">I
</span><span style="color:#b3933a;">2024</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">08</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">02
</span><span>$ date </span><span style="color:#72ab00;">-</span><span>d </span><span style="color:#d07711;">'+1 month 4 days'
</span><span style="color:#5597d6;">Friday </span><span style="color:#b3933a;">06 </span><span style="color:#5597d6;">September </span><span style="color:#b3933a;">2024 01</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">24</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">44 </span><span style="color:#5597d6;">PM IST
</span><span>
</span><span style="color:#7f8989;"># same as: date -d '-20 days' +%F
</span><span style="color:#7f8989;"># you can also use '20 days ago'
</span><span>$ date </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">I </span><span style="color:#72ab00;">-</span><span>d </span><span style="color:#d07711;">'-20 days'
</span><span style="color:#b3933a;">2024</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">07</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">13
</span></code></pre>
<p>For my <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> newsletter, I use a script to generate a template issue. I use the arithmetic feature as shown below:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># prev_date variable gets the value from the previous newsletter issue
</span><span>$ prev_date=</span><span style="color:#d07711;">'2024-02-23'
</span><span>$ date </span><span style="color:#72ab00;">-</span><span>d </span><span style="color:#d07711;">"$prev_date"' +7 days' </span><span style="color:#72ab00;">+%</span><span style="color:#5597d6;">F
</span><span style="color:#b3933a;">2024</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">03</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">01
</span></code></pre>
<br>
<h2 id="resource-links">Resource links</h2>
<ul>
<li><a href="https://www.gnu.org/software/coreutils/manual/coreutils.html#date-invocation">GNU date command manual</a></li>
<li><a href="https://www.gnu.org/software/coreutils/manual/coreutils.html#Examples-of-date">"Examples of date" section from the manual</a></li>
<li><a href="https://unix.stackexchange.com/questions/tagged/date?tab=Votes">Top voted Q&A on unix.stackexchange for the date command tag</a></li>
</ul>
Linux Command Line Computing book announcement2024-05-29T00:00:00+00:002024-05-29T00:00:00+00:00https://learnbyexample.github.io/linux-command-line-computing-announcement/<p>Hello!</p>
<p>I am pleased to announce a new version of my <a href="https://github.com/learnbyexample/cli-computing">Linux Command Line Computing</a> ebook. This is the longest book I've published so far (204 pages) — it took me more than 7 months to complete the first version and another month for a minor revision.</p>
<p>This ebook aims to teach <strong>Linux command line tools and Shell Scripting</strong> for <strong>beginner to intermediate</strong> level users. The main focus is towards <strong>managing your files</strong> and performing <strong>text processing tasks</strong>. Plenty of <strong>examples</strong> are provided to make it easier to understand a particular tool and its various features. <strong>Exercises</strong> at the end of chapters will help you practice what you've learned and <strong>solutions</strong> are provided for reference. I hope this ebook would make it much easier for you to discover CLI tools, features and learning resources than my own blundering experience.</p>
<span id="continue-reading"></span><br>
<h2 id="release-offers">Release offers<a class="zola-anchor" href="#release-offers" aria-label="Anchor link for: release-offers">🔗</a></h2>
<p>To celebrate the new release, you can download PDF/EPUB versions of the ebook for FREE till 9-June-2024. You can still pay if you wish ;)</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/cli_computing">https://learnbyexample.gumroad.com/l/cli_computing</a></li>
<li><a href="https://leanpub.com/cli_computing/c/new_cli_computing_release">https://leanpub.com/cli_computing/c/new_cli_computing_release</a></li>
</ul>
<p>Some of my bundles are on sale as well:</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/all-books/new_cli_computing_release">All books bundle</a> is $12 (normal price $32) — all my 13 programming ebooks</li>
<li><a href="https://learnbyexample.gumroad.com/l/linux-cli-text-processing/new_cli_computing_release">Linux CLI Text Processing</a> bundle is $7 (normal price $20) — Linux CLI tools, shell scripting, grep, sed, awk, perl and ruby one-liners</li>
</ul>
<br>
<h2 id="what-s-new">What's new?<a class="zola-anchor" href="#what-s-new" aria-label="Anchor link for: what-s-new">🔗</a></h2>
<ul>
<li>Some of the examples, exercises, descriptions and external links were updated/corrected</li>
<li>Book title changed to <strong>Linux Command Line Computing</strong></li>
<li>New cover image</li>
</ul>
<br>
<h2 id="videos">Videos<a class="zola-anchor" href="#videos" aria-label="Anchor link for: videos">🔗</a></h2>
<p>Here's a short video about the <strong>Linux Command Line Computing</strong> ebook:</p>
<p align="center"><iframe width="560" height="315" src="https://www.youtube.com/embed/vedRFbWwx_c" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<p>On this blog, I <a href="https://learnbyexample.github.io/tips/">post tips</a> covering Python, command line tools and Vim. Here are video demos for these tips:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=THSMmCZQn1A&list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F">Python tips</a></li>
<li><a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY">Linux command line tips</a></li>
</ul>
<br>
<h2 id="testimonials">Testimonials<a class="zola-anchor" href="#testimonials" aria-label="Anchor link for: testimonials">🔗</a></h2>
<blockquote>
<p>Ive only gotten through first pages but appears a good Unix/bash primer. I’ll probably recommend for new hires out of bootcamp because they’re usually weak here</p>
<p>— <a href="https://twitter.com/Lizziness/status/1589866691974291456">feedback on twitter</a></p>
</blockquote>
<blockquote>
<p>Nice book! I just started trying to get into linux today and you have some tips I haven’t found elsewhere and the text is an enjoyable read so far.</p>
<p>— <a href="https://old.reddit.com/r/linux4noobs/comments/1adrx6c/linux_guide_for_beginners/kk3dypr/">feedback on reddit</a></p>
</blockquote>
<br>
<h2 id="table-of-contents">Table of Contents<a class="zola-anchor" href="#table-of-contents" aria-label="Anchor link for: table-of-contents">🔗</a></h2>
<ol>
<li>Preface</li>
<li>Introduction and Setup</li>
<li>Command Line Overview</li>
<li>Managing Files and Directories</li>
<li>Shell Features</li>
<li>Viewing Part or Whole File Contents</li>
<li>Searching Files and Filenames</li>
<li>File Properties</li>
<li>Managing Processes</li>
<li>Multipurpose Text Processing Tools</li>
<li>Sorting Stuff</li>
<li>Comparing Files</li>
<li>Assorted Text Processing Tools</li>
<li>Shell Scripting</li>
<li>Shell Customization</li>
</ol>
<br>
<h2 id="web-version">Web version<a class="zola-anchor" href="#web-version" aria-label="Anchor link for: web-version">🔗</a></h2>
<p>You can also read the book online here: <a href="https://learnbyexample.github.io/cli-computing/">https://learnbyexample.github.io/cli-computing/</a>.</p>
<br>
<h2 id="github-repo">GitHub repo<a class="zola-anchor" href="#github-repo" aria-label="Anchor link for: github-repo">🔗</a></h2>
<p>Visit <a href="https://github.com/learnbyexample/cli-computing">https://github.com/learnbyexample/cli-computing</a> for markdown source, example files, exercise solutions, sample chapters and other details related to the book.</p>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://learnbyexample.github.io/customizing-pandoc/">my blog post</a> on how to customize <code>pandoc</code> for generating beautiful PDF/EPUB versions from GitHub style markdown.</p>
</blockquote>
<br>
<h2 id="newsletter">Newsletter<a class="zola-anchor" href="#newsletter" aria-label="Anchor link for: newsletter">🔗</a></h2>
<p>Subscribe to <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> — free newsletter covering programming resources, updates on what I am creating, tools, free ebooks and more, delivered every Friday.</p>
<br>
<h2 id="feedback-and-errata">Feedback and Errata<a class="zola-anchor" href="#feedback-and-errata" aria-label="Anchor link for: feedback-and-errata">🔗</a></h2>
<p>I would highly appreciate it if you'd <strong>let me know how you felt about this book</strong>. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.</p>
<p>You can reach me via:</p>
<ul>
<li>Issue Manager: <a href="https://github.com/learnbyexample/cli-computing/issues">https://github.com/learnbyexample/cli-computing/issues</a></li>
<li>E-mail: <code>echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode</code></li>
<li>Twitter: <a href="https://twitter.com/learn_byexample">https://twitter.com/learn_byexample</a></li>
</ul>
<p>Happy learning :)</p>
Interactive GNU awk tutorial2024-04-30T00:00:00+00:002024-04-30T00:00:00+00:00https://learnbyexample.github.io/interactive-awk-tutorial/<p>Know command line basics and want to learn the <code>GNU awk</code> command? Check out my interactive <a href="https://github.com/learnbyexample/TUI-apps/tree/main/AwkTutorial">TUI app</a> that gives a brief tour of this popular text processing command.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/AwkTutorial/awk_tutorial.png" alt="Sample screenshot for interactive awk tutorial" loading="lazy" /></p>
<span id="continue-reading"></span><br>
<h2 id="installation">Installation<a class="zola-anchor" href="#installation" aria-label="Anchor link for: installation">🔗</a></h2>
<p>This app is available on PyPI as <a href="https://pypi.org/project/awktutorial/">awktutorial</a>. Example installation instructions are shown below, adjust them based on your preferences and OS.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># virtual environment
</span><span style="color:#5597d6;">$</span><span> python3</span><span style="color:#5597d6;"> -m</span><span> venv textual_apps
</span><span style="color:#5597d6;">$</span><span> cd textual_apps
</span><span style="color:#5597d6;">$</span><span> source bin/activate
</span><span style="color:#5597d6;">$</span><span> pip install awktutorial
</span><span>
</span><span style="color:#7f8989;"># launch the app
</span><span style="color:#5597d6;">$</span><span> awktutorial
</span></code></pre>
<p>To run the app without having to enter the virtual environment again, add this alias to <code>.bashrc</code> (or equivalent):</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># you'll have to change the path
</span><span style="color:#b39f04;">alias </span><span style="color:#c23f31;">awktutorial</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'/path/to/textual_apps/bin/awktutorial'
</span></code></pre>
<p>As an alternative to manually managing such virtual environments, you can use <a href="https://github.com/pypa/pipx">https://github.com/pypa/pipx</a> instead:</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> pipx install awktutorial
</span><span style="color:#5597d6;">$</span><span> awktutorial
</span></code></pre>
<p>As yet another alternative, you can install <code>textual</code> (see <a href="https://textual.textualize.io/getting_started/">Textual documentation</a> for more details), clone my <a href="https://github.com/learnbyexample/TUI-apps">TUI-apps repository</a> repository and run the <code>awk_tutorial.py</code> file.</p>
<p>Adjust the terminal dimensions for the widgets to appear properly, for example 84x25 (characters x lines).</p>
<br>
<h2 id="ebook">Ebook<a class="zola-anchor" href="#ebook" aria-label="Anchor link for: ebook">🔗</a></h2>
<p>See my <a href="https://github.com/learnbyexample/learn_gnuawk">CLI text processing with GNU awk</a> ebook to learn <code>GNU awk</code> with hundreds of examples and exercises.</p>
<br>
<h2 id="feedback">Feedback<a class="zola-anchor" href="#feedback" aria-label="Anchor link for: feedback">🔗</a></h2>
<p>I'd highly appreciate your feedback. Please file <a href="https://github.com/learnbyexample/TUI-apps/issues">an issue</a> if there are bugs, crashes, etc.</p>
<p>Hope you'll find this TUI app useful. Happy learning :)</p>
CLI computation with GNU datamash2024-04-09T00:00:00+00:002025-05-16T00:00:00+00:00https://learnbyexample.github.io/cli-computation-gnu-datamash/<p>I'm hoping this post will serve as a quick reference for some of the use cases and tickle your curiosity if you haven't come across this nifty CLI text processing tool yet. There are also links for further reading at the end.</p>
<span id="continue-reading"></span><br>
<h2 id="installation-and-documentation">Installation and Documentation<a class="zola-anchor" href="#installation-and-documentation" aria-label="Anchor link for: installation-and-documentation">🔗</a></h2>
<p>See <a href="https://www.gnu.org/software/datamash/download/">download</a> page for source code and instructions to install the software on various platforms. This blog post is based on the <strong>1.8</strong> version.</p>
<p>See <a href="https://www.gnu.org/software/datamash/manual/">datamash manual</a> for links to documentation in HTML, plain text, PDF, etc.</p>
<br>
<h2 id="sum">Sum<a class="zola-anchor" href="#sum" aria-label="Anchor link for: sum">🔗</a></h2>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># file with a single number per line
</span><span>$ cat nums.txt
</span><span style="color:#b3933a;">42
</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">2
</span><span style="color:#b3933a;">10101
</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">3.14
</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">75
</span><span>$ datamash sum </span><span style="color:#b3933a;">1 </span><span style="color:#72ab00;"><</span><span>nums.txt
</span><span style="color:#b3933a;">10062.86
</span><span>
</span><span>$ echo </span><span style="color:#d07711;">'3.14 42 1000 -51' </span><span style="color:#72ab00;">|</span><span> tr </span><span style="color:#d07711;">' ' '\n' </span><span style="color:#72ab00;">|</span><span> datamash sum </span><span style="color:#b3933a;">1
</span><span style="color:#b3933a;">994.14
</span><span>
</span><span style="color:#7f8989;"># summing a particular column
</span><span style="color:#7f8989;"># tab is the default field separator
</span><span>$ cat table.txt
</span><span>brown bread mat hair </span><span style="color:#b3933a;">42
</span><span>blue cake mug shirt </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">7
</span><span>yellow banana window shoes </span><span style="color:#b3933a;">3.14
</span><span>$ datamash </span><span style="color:#72ab00;">-</span><span>t</span><span style="color:#d07711;">' '</span><span> sum </span><span style="color:#b3933a;">5 </span><span style="color:#72ab00;"><</span><span>table.txt
</span><span style="color:#b3933a;">38.14
</span></code></pre>
<p>Other such operations include <code>count</code>, <code>min</code>, <code>max</code>, <code>mean</code>, <code>median</code>, <code>sstdev</code> (standard deviation), etc.</p>
<br>
<h2 id="transpose-and-reverse">Transpose and Reverse<a class="zola-anchor" href="#transpose-and-reverse" aria-label="Anchor link for: transpose-and-reverse">🔗</a></h2>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat scores.csv
</span><span style="color:#5597d6;">Name</span><span>,</span><span style="color:#5597d6;">Maths</span><span>,</span><span style="color:#5597d6;">Physics</span><span>,</span><span style="color:#5597d6;">Chemistry
</span><span style="color:#5597d6;">Ith</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">100
</span><span style="color:#5597d6;">Cy</span><span>,</span><span style="color:#b3933a;">97</span><span>,</span><span style="color:#b3933a;">98</span><span>,</span><span style="color:#b3933a;">95
</span><span style="color:#5597d6;">Lin</span><span>,</span><span style="color:#b3933a;">78</span><span>,</span><span style="color:#b3933a;">83</span><span>,</span><span style="color:#b3933a;">80
</span><span style="color:#5597d6;">Er</span><span>,</span><span style="color:#b3933a;">60</span><span>,</span><span style="color:#b3933a;">70</span><span>,</span><span style="color:#b3933a;">90
</span><span>
</span><span style="color:#7f8989;"># interchange rows and columns
</span><span>$ datamash </span><span style="color:#72ab00;">-</span><span>t, transpose </span><span style="color:#72ab00;"><</span><span>scores.csv
</span><span style="color:#5597d6;">Name</span><span>,</span><span style="color:#5597d6;">Ith</span><span>,</span><span style="color:#5597d6;">Cy</span><span>,</span><span style="color:#5597d6;">Lin</span><span>,</span><span style="color:#5597d6;">Er
</span><span style="color:#5597d6;">Maths</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">97</span><span>,</span><span style="color:#b3933a;">78</span><span>,</span><span style="color:#b3933a;">60
</span><span style="color:#5597d6;">Physics</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">98</span><span>,</span><span style="color:#b3933a;">83</span><span>,</span><span style="color:#b3933a;">70
</span><span style="color:#5597d6;">Chemistry</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">95</span><span>,</span><span style="color:#b3933a;">80</span><span>,</span><span style="color:#b3933a;">90
</span><span>
</span><span style="color:#7f8989;"># reverse columns
</span><span>$ datamash </span><span style="color:#72ab00;">-</span><span>t, reverse </span><span style="color:#72ab00;"><</span><span>scores.csv
</span><span style="color:#5597d6;">Chemistry</span><span>,</span><span style="color:#5597d6;">Physics</span><span>,</span><span style="color:#5597d6;">Maths</span><span>,</span><span style="color:#5597d6;">Name
</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#5597d6;">Ith
</span><span style="color:#b3933a;">95</span><span>,</span><span style="color:#b3933a;">98</span><span>,</span><span style="color:#b3933a;">97</span><span>,</span><span style="color:#5597d6;">Cy
</span><span style="color:#b3933a;">80</span><span>,</span><span style="color:#b3933a;">83</span><span>,</span><span style="color:#b3933a;">78</span><span>,</span><span style="color:#5597d6;">Lin
</span><span style="color:#b3933a;">90</span><span>,</span><span style="color:#b3933a;">70</span><span>,</span><span style="color:#b3933a;">60</span><span>,</span><span style="color:#5597d6;">Er
</span></code></pre>
<br>
<h2 id="group-by">Group by<a class="zola-anchor" href="#group-by" aria-label="Anchor link for: group-by">🔗</a></h2>
<p>You can use the <code>-g</code> option to group items based on one or more columns. You can specify an operation such as <code>collapse</code>, <code>sum</code>, <code>mean</code>, <code>count</code> and so on. See <a href="https://unix.stackexchange.com/q/779049/109046">Grouping rows by categories avoiding repetition</a> for an example with <code>unique</code>.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># here, the first column items are already next to each other
</span><span style="color:#7f8989;"># so, sorting is not needed
</span><span>$ cat toys.txt
</span><span>car blue
</span><span>car red
</span><span>car yellow
</span><span>truck brown
</span><span>bus green
</span><span>bus maroon
</span><span>rocket white
</span><span>
</span><span style="color:#7f8989;"># by default a comma is used as the separator between collapsed items
</span><span style="color:#7f8989;"># use 'unique' instead of 'collapse' to avoid duplicates
</span><span>$ datamash </span><span style="color:#72ab00;">-</span><span>t</span><span style="color:#d07711;">' ' </span><span style="color:#72ab00;">-</span><span>g1 collapse </span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;"><</span><span>toys.txt
</span><span>car blue,red,yellow
</span><span>truck brown
</span><span>bus green,maroon
</span><span>rocket white
</span><span>
</span><span style="color:#7f8989;"># 'count' gives the number of items for the collapsed row
</span><span style="color:#7f8989;"># 'rand' selects a random item for such collapsed rows
</span><span style="color:#7f8989;"># 'first' and 'last' are other choices available
</span><span>$ datamash </span><span style="color:#72ab00;">-</span><span>t</span><span style="color:#d07711;">' ' </span><span style="color:#72ab00;">-</span><span>g1 count </span><span style="color:#b3933a;">2 </span><span style="color:#b39f04;">rand </span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;"><</span><span>toys.txt
</span><span>car </span><span style="color:#b3933a;">3</span><span> red
</span><span>truck </span><span style="color:#b3933a;">1</span><span> brown
</span><span>bus </span><span style="color:#b3933a;">2</span><span> green
</span><span>rocket </span><span style="color:#b3933a;">1</span><span> white
</span></code></pre>
<p>Here's an example with header lines as well as having to sort the input (<code>-s</code>). The <code>-c</code> option helps to customize the separator for the grouped items. The <code>-H</code> option is equivalent to using both <code>--header-in</code> and <code>--header-out</code>.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span>$ cat books.txt
</span><span>Author,Title
</span><span>Will Wight,Cradle
</span><span>John Bierce,Mage Errant
</span><span>Brandon Sanderson,Mistborn
</span><span>Domagoj Kurmaic,Mother of Learning
</span><span>Brandon Sanderson,The Stormlight Archive
</span><span>Will Wight,The Last Horizon
</span><span>Brandon Sanderson,Warbreaker
</span><span>
</span><span style="color:#7f8989;"># not sure if there's an option to retain the original header line as is
</span><span style="color:#7f8989;"># you can instead use: (sed -u 1q; datamash -st, -c: -g1 collapse 2) <books.txt
</span><span style="color:#7f8989;"># use --header-in if you don't want the header line in the output
</span><span>$ datamash </span><span style="color:#72ab00;">-</span><span>H </span><span style="color:#72ab00;">-</span><span>st, </span><span style="color:#72ab00;">-</span><span>c: </span><span style="color:#72ab00;">-</span><span>g1 collapse </span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;"><</span><span>books.txt
</span><span style="color:#5597d6;">GroupBy</span><span>(Author),</span><span style="color:#5597d6;">collapse</span><span>(Title)
</span><span>Brandon Sanderson,Mistborn:The Stormlight Archive:Warbreaker
</span><span>Domagoj Kurmaic,Mother of Learning
</span><span>John Bierce,Mage Errant
</span><span>Will Wight,Cradle:The Last Horizon
</span></code></pre>
<p>Here's an example of summing values based on column 3 items:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat duplicates.csv
</span><span>brown,toy,bread,</span><span style="color:#b3933a;">42
</span><span>dark red,ruby,rose,</span><span style="color:#b3933a;">111
</span><span>blue,ruby,water,</span><span style="color:#b3933a;">333
</span><span>dark red,sky,rose,</span><span style="color:#b3933a;">555
</span><span>yellow,toy,flower,</span><span style="color:#b3933a;">333
</span><span>white,sky,bread,</span><span style="color:#b3933a;">111
</span><span>light red,purse,rose,</span><span style="color:#b3933a;">333
</span><span>
</span><span>$ datamash </span><span style="color:#72ab00;">-</span><span>st, </span><span style="color:#72ab00;">-</span><span>g3 sum </span><span style="color:#b3933a;">4 </span><span style="color:#72ab00;"><</span><span>duplicates.csv
</span><span>bread,</span><span style="color:#b3933a;">153
</span><span>flower,</span><span style="color:#b3933a;">333
</span><span>rose,</span><span style="color:#b3933a;">999
</span><span>water,</span><span style="color:#b3933a;">333
</span></code></pre>
<p>Average marks:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat result.csv
</span><span style="color:#5597d6;">Amy</span><span>,maths,</span><span style="color:#b3933a;">90
</span><span style="color:#5597d6;">Amy</span><span>,physics,</span><span style="color:#b3933a;">75
</span><span style="color:#5597d6;">Joe</span><span>,maths,</span><span style="color:#b3933a;">79
</span><span style="color:#5597d6;">John</span><span>,chemistry,</span><span style="color:#b3933a;">77
</span><span style="color:#5597d6;">John</span><span>,physics,</span><span style="color:#b3933a;">91
</span><span style="color:#5597d6;">Moe</span><span>,maths,</span><span style="color:#b3933a;">81
</span><span style="color:#5597d6;">Ravi</span><span>,physics,</span><span style="color:#b3933a;">84
</span><span style="color:#5597d6;">Ravi</span><span>,chemistry,</span><span style="color:#b3933a;">70
</span><span style="color:#5597d6;">Yui</span><span>,maths,</span><span style="color:#b3933a;">92
</span><span>
</span><span>$ datamash </span><span style="color:#72ab00;">-</span><span>t, </span><span style="color:#72ab00;">-</span><span>g1 mean </span><span style="color:#b3933a;">3 </span><span style="color:#72ab00;"><</span><span>result.csv
</span><span style="color:#5597d6;">Amy</span><span>,</span><span style="color:#b3933a;">82.5
</span><span style="color:#5597d6;">Joe</span><span>,</span><span style="color:#b3933a;">79
</span><span style="color:#5597d6;">John</span><span>,</span><span style="color:#b3933a;">84
</span><span style="color:#5597d6;">Moe</span><span>,</span><span style="color:#b3933a;">81
</span><span style="color:#5597d6;">Ravi</span><span>,</span><span style="color:#b3933a;">77
</span><span style="color:#5597d6;">Yui</span><span>,</span><span style="color:#b3933a;">92
</span></code></pre>
<br>
<h2 id="further-reading">Further Reading<a class="zola-anchor" href="#further-reading" aria-label="Anchor link for: further-reading">🔗</a></h2>
<ul>
<li><a href="https://www.gnu.org/software/datamash/alternatives/">Alternative one-liners</a> — examples with datamash compared to <code>awk</code>, <code>perl</code>, etc</li>
<li><a href="https://www.gnu.org/software/datamash/examples/">Documentation examples</a></li>
<li><a href="https://github.com/BurntSushi/xsv">xsv</a> — fast CSV command line toolkit</li>
</ul>
CLI text processing with GNU Coreutils book announcement2024-04-03T00:00:00+00:002024-05-08T00:00:00+00:00https://learnbyexample.github.io/cli-text-processing-coreutils-announcement/<p>Hello!</p>
<p>I am pleased to announce a new version of my <strong>CLI text processing with GNU Coreutils</strong> ebook. Examples, descriptions and external links were updated/corrected and 100+ exercises were added.</p>
<p>You might be already aware of popular coreutils commands like <code>head</code>, <code>tail</code>, <code>tr</code>, <code>sort</code> and so on. This book will teach you more than twenty of such specialized text processing tools provided by the <code>GNU coreutils</code> package.</p>
<span id="continue-reading"></span><br>
<h2 id="release-offers">Release offers<a class="zola-anchor" href="#release-offers" aria-label="Anchor link for: release-offers">🔗</a></h2>
<p>To celebrate the new release, you can download PDF/EPUB versions of the ebook for FREE till 10-April-2024. You can still pay if you wish ;)</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/cli_coreutils">Gumroad</a></li>
<li><a href="https://leanpub.com/cli_coreutils/c/new_coreutils_release">Leanpub</a></li>
</ul>
<p>The following bundles are heavily discounted:</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/all-books/new_coreutils_release">All books bundle</a> is $12 (normal price $32)</li>
<li><a href="https://learnbyexample.gumroad.com/l/linux-cli-text-processing/new_coreutils_release">Linux CLI Text Processing</a> bundle is $6 (normal price $20)</li>
</ul>
<br>
<h2 id="what-s-new">What's new?<a class="zola-anchor" href="#what-s-new" aria-label="Anchor link for: what-s-new">🔗</a></h2>
<ul>
<li><code>GNU coreutils</code> package version updated to <strong>9.1</strong></li>
<li>Added 100+ exercises</li>
<li>In general, many of the examples, descriptions and external links were updated/corrected</li>
<li>Updated Acknowledgements section</li>
<li>Code snippets related to info/warning sections will now appear as a single block</li>
<li>Book title changed to <strong>CLI text processing with GNU Coreutils</strong></li>
<li>New cover image</li>
</ul>
<br>
<h2 id="videos">Videos<a class="zola-anchor" href="#videos" aria-label="Anchor link for: videos">🔗</a></h2>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/oCnJLu_PUbY" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<p>On this blog, I <a href="https://learnbyexample.github.io/tips/">post tips</a> covering Python, command line tools and Vim. Here are video demos for these tips:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=THSMmCZQn1A&list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F">Python tips</a></li>
<li><a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY">Linux command line tips</a></li>
</ul>
<br>
<h2 id="testimonials">Testimonials<a class="zola-anchor" href="#testimonials" aria-label="Anchor link for: testimonials">🔗</a></h2>
<blockquote>
<p>In my opinion the book does a great job of quickly presenting examples of how commands can be used and then paired up to achieve new or interesting ways of manipulating data. Throughout the text there are little highlights offering tips on extra functionality or limitations of certain commands. For instance, when discussing the <em>shuf</em> command we're warned that <em>shuf</em> will not work with multiple files. However, we can merge multiple files together (using the <em>cat</em> command) and then pass them to <em>shuf</em>. These little gems of wisdom add a dimension to the book and will likely save the reader some time wondering why their scripts are not working as expected.</p>
<p>— book review by Jesse Smith on <a href="https://distrowatch.com/weekly.php?issue=20211206#book">distrowatch.com</a></p>
</blockquote>
<blockquote>
<p>I discovered your books recently and they’re awesome, thank you! As a 20 year *nix they made me realize how much more there are to these rock solid and ancient tools, once you spend the time to actually learn the intricacies of them.</p>
<p>— feedback on <a href="https://old.reddit.com/r/commandline/comments/1byumd6/learn_gnu_coreutils_text_processing_tools_like/l2pk5bd/">reddit</a></p>
</blockquote>
<br>
<h2 id="table-of-contents">Table of Contents<a class="zola-anchor" href="#table-of-contents" aria-label="Anchor link for: table-of-contents">🔗</a></h2>
<ol>
<li>Preface</li>
<li>Introduction</li>
<li>cat and tac</li>
<li>head and tail</li>
<li>tr</li>
<li>cut</li>
<li>seq</li>
<li>shuf</li>
<li>paste</li>
<li>pr</li>
<li>fold and fmt</li>
<li>sort</li>
<li>uniq</li>
<li>comm</li>
<li>join</li>
<li>nl</li>
<li>wc</li>
<li>split</li>
<li>csplit</li>
<li>expand and unexpand</li>
<li>basename and dirname</li>
<li>What next?</li>
</ol>
<br>
<h2 id="web-version">Web version<a class="zola-anchor" href="#web-version" aria-label="Anchor link for: web-version">🔗</a></h2>
<p>You can also read the book online here: <a href="https://learnbyexample.github.io/cli_text_processing_coreutils/introduction.html">https://learnbyexample.github.io/cli_text_processing_coreutils/introduction.html</a>.</p>
<br>
<h2 id="github-repo">GitHub repo<a class="zola-anchor" href="#github-repo" aria-label="Anchor link for: github-repo">🔗</a></h2>
<p>Visit <a href="https://github.com/learnbyexample/cli_text_processing_coreutils">https://github.com/learnbyexample/cli_text_processing_coreutils</a> for markdown source, example files, exercise solutions, sample chapters and other details related to the book.</p>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://learnbyexample.github.io/customizing-pandoc/">my blog post</a> on how to customize <code>pandoc</code> for generating beautiful PDF/EPUB versions from GitHub style markdown.</p>
</blockquote>
<br>
<h2 id="newsletter">Newsletter<a class="zola-anchor" href="#newsletter" aria-label="Anchor link for: newsletter">🔗</a></h2>
<p>Subscribe to <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> — free newsletter covering programming resources, updates on what I am creating, tools, free ebooks and more, delivered every Friday.</p>
<br>
<h2 id="feedback-and-errata">Feedback and Errata<a class="zola-anchor" href="#feedback-and-errata" aria-label="Anchor link for: feedback-and-errata">🔗</a></h2>
<p>I would highly appreciate it if you'd <strong>let me know how you felt about this book</strong>. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.</p>
<p>You can reach me via:</p>
<ul>
<li>Issue Manager: <a href="https://github.com/learnbyexample/cli_text_processing_coreutils/issues">https://github.com/learnbyexample/cli_text_processing_coreutils/issues</a></li>
<li>E-mail: <code>echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode</code></li>
<li>Twitter: <a href="https://twitter.com/learn_byexample">https://twitter.com/learn_byexample</a></li>
</ul>
<p>Happy learning :)</p>
Ruby One-Liners Guide book announcement2024-02-20T00:00:00+00:002024-02-26T00:00:00+00:00https://learnbyexample.github.io/ruby-oneliners-guide-announcement/<p>Hello!</p>
<p>I am pleased to announce a new version of my <strong>Ruby One-Liners Guide</strong> ebook. Examples, exercises, solutions, descriptions and external links were added/updated/corrected.</p>
<p>When it comes to command line text processing, there are several well known tools like <code>grep</code> for filtering, <code>sed</code> for substitution and <code>awk</code> for field processing. Compared to such tools, <strong>Ruby has a feature rich regular expression engine, plenty of builtin modules and a thriving ecosystem</strong>. Another advantage is that Ruby is more <strong>portable</strong>.</p>
<p>This ebook will show examples for filtering and substitution features, field processing, using standard and third-party modules, multiple file processing, how to construct solutions that depend on multiple records, how to compare records and fields between two or more files, how to identify duplicates while maintaining input order and so on.</p>
<span id="continue-reading"></span><br>
<h2 id="release-offers">Release offers<a class="zola-anchor" href="#release-offers" aria-label="Anchor link for: release-offers">🔗</a></h2>
<p>To celebrate the new release, you can download PDF/EPUB versions of <strong>Ruby One-Liners Guide</strong> for FREE till 29-February-2024. You can still pay if you wish ;)</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/ruby-oneliners">Gumroad</a></li>
<li><a href="https://leanpub.com/ruby-oneliners/c/new_release">Leanpub</a></li>
</ul>
<p><strong>Ruby Text Processing</strong> bundle is free as well:</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/ruby-textprocessing">Gumroad</a></li>
<li><a href="https://leanpub.com/b/ruby-textprocessing/c/new_release">Leanpub</a></li>
</ul>
<p>So is the <strong>Magical one-liners</strong> bundle:</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/oneliners">Gumroad</a></li>
<li><a href="https://leanpub.com/b/oneliners/c/new_release">Leanpub</a></li>
</ul>
<br>
<h2 id="what-s-new">What's new?<a class="zola-anchor" href="#what-s-new" aria-label="Anchor link for: what-s-new">🔗</a></h2>
<ul>
<li>Command version updated to <strong>Ruby 3.3.0</strong></li>
<li>Added more exercises</li>
<li>Long sections split into smaller ones</li>
<li>In general, many of the examples, exercises, solutions, descriptions and external links were updated/corrected</li>
<li>Updated Acknowledgements section</li>
<li>Code snippets related to info/warning sections will now appear as a single block</li>
<li>Book title changed to <strong>Ruby One-Liners Guide</strong></li>
<li>New cover image</li>
</ul>
<br>
<h2 id="videos">Videos<a class="zola-anchor" href="#videos" aria-label="Anchor link for: videos">🔗</a></h2>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/nsWVepZruws" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<p>On this blog, I <a href="https://learnbyexample.github.io/tips/">post tips</a> covering Python, command line tools and Vim. Here are video demos for these tips:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=THSMmCZQn1A&list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F">Python tips</a></li>
<li><a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY">Linux command line tips</a></li>
</ul>
<br>
<h2 id="testimonials">Testimonials<a class="zola-anchor" href="#testimonials" aria-label="Anchor link for: testimonials">🔗</a></h2>
<blockquote>
<p>This Ruby one-liners cookbook is incredible. Pretty mind boggling all the stuff you can do.</p>
<p>— <a href="https://twitter.com/jbrancha/status/1506766118756786189">feedback on twitter</a></p>
</blockquote>
<br>
<h2 id="table-of-contents">Table of Contents<a class="zola-anchor" href="#table-of-contents" aria-label="Anchor link for: table-of-contents">🔗</a></h2>
<ol>
<li>Preface</li>
<li>One-liner introduction</li>
<li>Line processing</li>
<li>Field separators</li>
<li>Record separators</li>
<li>Multiple file input</li>
<li>Processing multiple records</li>
<li>Two file processing</li>
<li>Dealing with duplicates</li>
<li>Processing structured data</li>
</ol>
<br>
<h2 id="web-version">Web version<a class="zola-anchor" href="#web-version" aria-label="Anchor link for: web-version">🔗</a></h2>
<p>You can also read the book online here: <a href="https://learnbyexample.github.io/learn_ruby_oneliners/">https://learnbyexample.github.io/learn_ruby_oneliners/</a>.</p>
<br>
<h2 id="github-repo">GitHub repo<a class="zola-anchor" href="#github-repo" aria-label="Anchor link for: github-repo">🔗</a></h2>
<p>Visit <a href="https://github.com/learnbyexample/learn_ruby_oneliners">https://github.com/learnbyexample/learn_ruby_oneliners</a> for markdown source, example files, exercise solutions, sample chapters and other details related to the book.</p>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://learnbyexample.github.io/customizing-pandoc/">my blog post</a> on how to customize <code>pandoc</code> for generating beautiful PDF/EPUB versions from GitHub style markdown.</p>
</blockquote>
<br>
<h2 id="newsletter">Newsletter<a class="zola-anchor" href="#newsletter" aria-label="Anchor link for: newsletter">🔗</a></h2>
<p>Subscribe to <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> — free newsletter covering programming resources, updates on what I am creating, tools, ebooks and more, delivered every Friday.</p>
<br>
<h2 id="feedback-and-errata">Feedback and Errata<a class="zola-anchor" href="#feedback-and-errata" aria-label="Anchor link for: feedback-and-errata">🔗</a></h2>
<p>I would highly appreciate it if you'd <strong>let me know how you felt about this book</strong>. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.</p>
<p>You can reach me via:</p>
<ul>
<li>Issue Manager: <a href="https://github.com/learnbyexample/learn_ruby_oneliners/issues">https://github.com/learnbyexample/learn_ruby_oneliners/issues</a></li>
<li>E-mail: <code>echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode</code></li>
<li>Twitter: <a href="https://twitter.com/learn_byexample">https://twitter.com/learn_byexample</a></li>
</ul>
<p>Happy learning :)</p>
Understanding Ruby Regexp book announcement2024-02-02T00:00:00+00:002024-02-02T00:00:00+00:00https://learnbyexample.github.io/understanding-ruby-regexp-announcement/<p>Hello!</p>
<p>I just published a new version of the "<strong>Understanding Ruby Regexp</strong>" ebook. Corrected examples and descriptions for Atomic grouping, <code>\G</code> and <code>\K</code> features, improved examples, exercises and so on.</p>
<p>This book will help you learn <strong>Ruby Regular Expressions</strong> step-by-step from beginner to advanced levels with <strong>hundreds of examples and exercises</strong>.</p>
<span id="continue-reading"></span><br>
<h2 id="ebook-links">Ebook links<a class="zola-anchor" href="#ebook-links" aria-label="Anchor link for: ebook-links">🔗</a></h2>
<p>You can download the PDF/EPUB versions of the book for free using the below links (you can also pay if you wish):</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/rubyregexp">Gumroad</a></li>
<li><a href="https://leanpub.com/rubyregexp">Leanpub</a></li>
</ul>
<p>You can also read the book online here: <a href="https://learnbyexample.github.io/Ruby_Regexp/">https://learnbyexample.github.io/Ruby_Regexp/</a>.</p>
<br>
<h2 id="what-s-new">What's new?<a class="zola-anchor" href="#what-s-new" aria-label="Anchor link for: what-s-new">🔗</a></h2>
<ul>
<li>Ruby version updated to <strong>3.3.0</strong></li>
<li>Corrected examples and descriptions for Atomic grouping, <code>\G</code> and <code>\K</code> features</li>
<li>In general, many of the examples, exercises, solutions, descriptions and external links were updated/corrected</li>
<li>Updated Acknowledgements section</li>
<li>Code snippets related to info/warning sections will now appear as a single block</li>
<li>Book title changed to <strong>Understanding Ruby Regexp</strong></li>
<li>New cover image</li>
<li>Images centered for EPUB format</li>
</ul>
<br>
<h2 id="videos">Videos<a class="zola-anchor" href="#videos" aria-label="Anchor link for: videos">🔗</a></h2>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/QNsCzVeZH78" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<p>On this blog, I <a href="https://learnbyexample.github.io/tips/">post tips</a> covering Python, command line tools and Vim. Here are video demos for these tips:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=THSMmCZQn1A&list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F">Python tips</a></li>
<li><a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY">Linux command line tips</a></li>
</ul>
<br>
<h2 id="table-of-contents">Table of Contents<a class="zola-anchor" href="#table-of-contents" aria-label="Anchor link for: table-of-contents">🔗</a></h2>
<ol>
<li>Preface</li>
<li>Why is it needed?</li>
<li>Regexp introduction</li>
<li>Anchors</li>
<li>Alternation and Grouping</li>
<li>Escaping metacharacters</li>
<li>Dot metacharacter and Quantifiers</li>
<li>Interlude: Tools for debugging and visualization</li>
<li>Working with matched portions</li>
<li>Character class</li>
<li>Groupings and backreferences</li>
<li>Interlude: Common tasks</li>
<li>Lookarounds</li>
<li>Modifiers</li>
<li>Unicode</li>
<li>Further Reading</li>
</ol>
<br>
<h2 id="github-repo">GitHub repo<a class="zola-anchor" href="#github-repo" aria-label="Anchor link for: github-repo">🔗</a></h2>
<p>Visit <a href="https://github.com/learnbyexample/Ruby_Regexp">https://github.com/learnbyexample/Ruby_Regexp</a> for markdown source, exercise solutions, sample chapters and other details related to the book.</p>
<p>See <a href="https://learnbyexample.github.io/customizing-pandoc/">my blog post</a> on how to customize <code>pandoc</code> for generating beautiful PDF/EPUB versions from GitHub style markdown.</p>
<br>
<h2 id="newsletter">Newsletter<a class="zola-anchor" href="#newsletter" aria-label="Anchor link for: newsletter">🔗</a></h2>
<p>Subscribe to <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> — free newsletter covering programming resources, updates on what I am creating, tools, ebooks and more, delivered every Friday.</p>
<br>
<h2 id="feedback-and-errata">Feedback and Errata<a class="zola-anchor" href="#feedback-and-errata" aria-label="Anchor link for: feedback-and-errata">🔗</a></h2>
<p>I would highly appreciate it if you'd <strong>let me know how you felt about this book</strong>. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.</p>
<p>You can reach me via:</p>
<ul>
<li>Issue Manager: <a href="https://github.com/learnbyexample/Ruby_Regexp/issues">https://github.com/learnbyexample/Ruby_Regexp/issues</a></li>
<li>E-mail: <code>learn by [email protected]</code> (without the spaces)</li>
<li>Twitter: <a href="https://twitter.com/learn_byexample">https://twitter.com/learn_byexample</a></li>
</ul>
<p>Happy learning :)</p>
2023: year in perspective2023-12-29T00:00:00+00:002024-01-01T00:00:00+00:00https://learnbyexample.github.io/2023-year-in-perspective/<p><strong>TL;DR</strong>: Updated six programming ebooks, created four interactive TUI apps for exercises, wrote blog posts, recorded YouTube videos, newsletter prospered, read 100+ novels, and so on. Had a great year in terms of ebook sales despite worries over AI tools 😇</p>
<span id="continue-reading"></span><br>
<h2 id="books-updated">Books updated<a class="zola-anchor" href="#books-updated" aria-label="Anchor link for: books-updated">🔗</a></h2>
<p>This year I focused on updating my existing ebooks instead of working on a new one. I managed to revise 6 out of my 13 published works so far. Examples and exercises were added and improved. Typos were corrected, sections added for new features (if any), new book covers, <a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4NdoeZIhEAPgPhojD8kvRdQ">promo videos</a> and so on.</p>
<ul>
<li><a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> — Learn Python Regular Expressions step-by-step from beginner to advanced levels with 300+ examples</li>
<li><a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep">CLI text processing with GNU grep and ripgrep</a> — Example based guide to mastering GNU grep and ripgrep</li>
<li><a href="https://github.com/learnbyexample/learn_gnused">CLI text processing with GNU sed</a> — Example based guide to mastering GNU sed</li>
<li><a href="https://github.com/learnbyexample/learn_gnuawk">CLI text processing with GNU awk</a> — Example based guide to mastering GNU awk one-liners</li>
<li><a href="https://github.com/learnbyexample/learn_perl_oneliners">Perl One-Liners Guide</a> — Example based guide for text processing with Perl from the command line</li>
<li><a href="https://github.com/learnbyexample/learn_js_regexp">Understanding JavaScript RegExp</a> — Learn JavaScript Regular Expressions step-by-step from beginner to advanced levels with hundreds of examples and exercises</li>
</ul>
<br>
<h2 id="tui-apps">TUI apps<a class="zola-anchor" href="#tui-apps" aria-label="Anchor link for: tui-apps">🔗</a></h2>
<p>Last year, I had learned a bit of <a href="https://textual.textualize.io/">Textual</a>. My aim was to create interactive apps for practicing exercises from my ebooks. I wrote the following apps:</p>
<ul>
<li><a href="https://github.com/learnbyexample/TUI-apps/tree/main/PyRegexExercises">Python re(gex)? exercises</a> — 100+ exercises for Python Regular Expressions
<ul>
<li><a href="https://github.com/learnbyexample/TUI-apps/tree/main/PyRegexPlayground">Python re(gex)? playground</a> — interactive playground, also includes a cheatsheet</li>
</ul>
</li>
<li><a href="https://github.com/learnbyexample/TUI-apps/tree/main/GrepExercises">Grep Exercises</a> — 50+ exercises for <code>GNU grep</code> (or alternate implementations like <code>ripgrep</code>)</li>
<li><a href="https://github.com/learnbyexample/TUI-apps/tree/main/SedExercises">Sed Exercises</a> — 50+ exercises for <code>GNU sed</code></li>
<li><a href="https://github.com/learnbyexample/TUI-apps/tree/main/AwkExercises">Awk Exercises</a> — 80+ exercises for <code>GNU awk</code></li>
</ul>
<p>And I also added more exercises for the <a href="https://github.com/learnbyexample/TUI-apps/tree/main/CLI-Exercises">Linux CLI Text Processing Exercises</a> app.</p>
<br>
<h2 id="blog-posts">Blog posts<a class="zola-anchor" href="#blog-posts" aria-label="Anchor link for: blog-posts">🔗</a></h2>
<p>Most of my blog posts this year were related to book and interactive app announcements. So, not really a choice to pick favorites from:</p>
<ul>
<li><a href="https://learnbyexample.github.io/python-regex-surprises/">Python Regex Surprises</a></li>
<li><a href="https://learnbyexample.github.io/mini/cli-text-editing-with-ed/">CLI text editing with ed</a></li>
</ul>
<p>I also posted some <a href="https://learnbyexample.github.io/tips/">weekly programming tips</a> (Python, Linux, Vim).</p>
<br>
<h2 id="book-sales">Book sales<a class="zola-anchor" href="#book-sales" aria-label="Anchor link for: book-sales">🔗</a></h2>
<p>Revenue from ebook sales were about 10% lower than last year. At the start of the year, I'd have been satisfied even if it had been 50% lower. I wasn't writing new ebooks and AI tools were all the rage on social media. Somehow, I got lucky with self-promotion posts for my <code>GNU awk</code> ebook and the rest of the months weren't too shabby. Here's my Gumroad revenue chart for 2023:</p>
<p align="center"><img src="/images/books/gumroad_sales_2023.png" alt="Gumroad sales in 2023" /></p>
<p>You can clearly see when the <code>GNU awk</code> ebook was updated. Sales on Gumroad was actually just a bit higher than last year. It was on Leanpub that sales were much lower, almost half compared to last year. Profits reduced more than 10% since Gumroad increased their fees. Overall, I'm still earning more than I need and I'm hoping that next year wouldn't see too much drop in sales.</p>
<br>
<h2 id="newsletter">Newsletter<a class="zola-anchor" href="#newsletter" aria-label="Anchor link for: newsletter">🔗</a></h2>
<p>I started a newsletter, <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a>, two years back. I've managed to send an email every Friday without fail so far and I'm proud of that. Sometimes I had to schedule issues weeks ahead. Total subscriber count crossed 1000 earlier this month and some readers are even paying me monthly despite this being a free newsletter.</p>
<br>
<h2 id="fictional-reading">Fictional reading<a class="zola-anchor" href="#fictional-reading" aria-label="Anchor link for: fictional-reading">🔗</a></h2>
<p>I enjoy reading fantasy and science-fiction novels. I read 100+ SFF books this year despite aiming for less than 100! Anyway, I wrote a post listing <a href="https://learnbyexample.github.io/escapist-reviews/lists/2023-favorite-sff-novels/">my favorites here</a>.</p>
<p>I even participated in NaNoWriMo. I only wrote 20K words, but I did have some fun. The novel went nowhere though and it is languishing now. Not sure if I'd get back to it someday.</p>
<br>
<h2 id="goals-for-2024">Goals for 2024<a class="zola-anchor" href="#goals-for-2024" aria-label="Anchor link for: goals-for-2024">🔗</a></h2>
<p>There are seven more books I need to update. Hopefully I get them done in a year, though I won't be pushing hard. If I crave to write some new books instead, I'd switch over to them. Or even do something else entirely. After more than six years writing tutorials and books, I sure can do with a break.</p>
<br>
<p>Here's wishing you a very happy, healthy and prosperous 2024 👍 😇</p>
Festive offers for books on Python, Linux, Regular Expressions and more2023-11-18T00:00:00+00:002023-11-25T00:00:00+00:00https://learnbyexample.github.io/programming-deals-2023/<p>Hello!</p>
<p>Here are some exciting deals for my programming ebooks as well as from other creators.</p>
<span id="continue-reading"></span><br>
<h2 id="my-ebooks">My ebooks<a class="zola-anchor" href="#my-ebooks" aria-label="Anchor link for: my-ebooks">🔗</a></h2>
<p>Offers valid till 30-Nov-2023:</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer">All 13 Books Bundle</a> — $10 (normal price $32)</li>
<li><a href="https://learnbyexample.gumroad.com/l/python-bundle/FestiveOffer">Learn by example Python bundle</a> — $4 (normal price $15)</li>
<li><a href="https://learnbyexample.gumroad.com/l/py_regex/FestiveOffer">Understanding Python re(gex)?</a> — FREE (normal price $10)</li>
</ul>
<p align="center"><a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer"><img src="/images/books/all_books_bundle.png" alt="All books bundle" loading="lazy" /></a></p>
<br>
<h2 id="indie-creators">Indie creators<a class="zola-anchor" href="#indie-creators" aria-label="Anchor link for: indie-creators">🔗</a></h2>
<ul>
<li><a href="https://mathspp.gumroad.com/l/pythonbootcamp/bootcampbf23">Python Problem-Solving Bootcamp</a> — 40% off or purchasing power parity discount whichever is greater</li>
<li><a href="https://driscollis.gumroad.com/">Python books by Michael Driscoll</a> and <a href="https://www.teachmepython.com/">Teach Me Python Membership</a> — 33% off with <code>black23</code> discount code</li>
<li><a href="https://adamchainz.gumroad.com/">Ebooks on Django and Git</a> — 50% off, plus purchasing power parity if applicable
<ul>
<li>see also author's <a href="https://adamj.eu/tech/2023/11/20/django-black-friday-deals-2023/">blog post</a> for links to other Django-related deals</li>
</ul>
</li>
<li><a href="https://thepythoncodingplace.thinkific.com/bundles/the-python-coding-place-membership">The Python Coding Place Membership</a> — 70% off</li>
<li><a href="https://www.pythonmorsels.com/lifetime-access-sale/">Python Morsels Membership</a> — lifetime access for the price of 2 years
<ul>
<li>see also author's <a href="https://treyhunner.com/2023/11/python-black-friday-and-cyber-monday-sales-2023/">blog post</a> for links to other Python deals</li>
</ul>
</li>
<li><a href="https://bhavaniravi.gumroad.com/l/LaFSj/P2PBLACK2023">Python To Projects - 5 Week Online Course</a> — 25% off</li>
<li><a href="https://lernerpython.com/bfcm-2023/">Python books by Reuven Lerner</a> — 40% off</li>
<li><a href="https://wizardzines.com/">wizard zines</a> — 50% off on PDFs, 30% off on print versions</li>
<li><a href="https://shrutibalasa.gumroad.com/l/level-up-with-tailwind-css/blackfriday23">Level up with Tailwind CSS</a> and <a href="https://shrutibalasa.gumroad.com/l/css-flex-and-grid/blackfriday23">Complete Guide to CSS Flex and Grid</a> — 50% off</li>
</ul>
<br>
<h2 id="miscellaneous">Miscellaneous<a class="zola-anchor" href="#miscellaneous" aria-label="Anchor link for: miscellaneous">🔗</a></h2>
<ul>
<li><a href="https://nostarch.com/blog/2023-holiday-gift-guide">NoStarch Press</a> — 35% off with <code>DEALS4DAYS</code> code</li>
<li><a href="https://media.pragprog.com/newsletters/2023-11-17.html">The Pragmatic Bookshelf</a> — 40% off on all ebooks and audio books</li>
<li><a href="https://deals.manning.com/buy-2-save-50/">Manning Publications</a> — save 50% when you buy 2 or more MEAPs, eBooks, pBooks, liveProjects, or liveVideos</li>
<li><a href="https://github.com/0x90n/InfoSec-Black-Friday">InfoSec Hack Friday</a> — InfoSec related software/tools</li>
<li><a href="https://opsdisk.gumroad.com/l/cphlab/blackfriday2023">The Cyber Plumber's Lab Guide and Interactive Access</a> — 33% OFF</li>
<li><a href="https://mailchi.mp/leanpub/monthly-sale-2023-black-friday">Leanpub Monthly Sale</a> and <a href="https://mailchi.mp/leanpub/weekly-sale-2023-black-friday">Leanpub Weekly Sale</a> — offers for programming books, bundles and courses</li>
<li><a href="https://github.com/trungdq88/Awesome-Black-Friday-Cyber-Monday">Huge list of awesome deals</a> — tools, productivity, books, courses, etc</li>
<li><a href="https://blackfridaydeals.dev/">blackfridaydeals.dev</a> — Hottest Black Friday Deals for Developers</li>
<li><a href="https://blog.diversifytech.com/black-friday-deals-2023-savings-on-tech-books-and-courses/">Black Friday Deals</a> — Savings on Tech Books and Courses</li>
</ul>
<br>
<p>Happy learning :)</p>
Understanding JavaScript RegExp book announcement2023-10-26T00:00:00+00:002023-10-26T00:00:00+00:00https://learnbyexample.github.io/understanding-javascript-regexp-announcement/<p>Hello!</p>
<p>I just published a new version of "<strong>Understanding JavaScript RegExp</strong>" ebook. Added examples for <code>d</code> and <code>v</code> flags, corrected many mistakes, improved examples, exercises and so on.</p>
<p>This book will help you learn <strong>JavaScript Regular Expressions</strong> step-by-step from beginner to advanced levels with <strong>hundreds of examples and exercises</strong>.</p>
<span id="continue-reading"></span><br>
<h2 id="release-offers">Release offers<a class="zola-anchor" href="#release-offers" aria-label="Anchor link for: release-offers">🔗</a></h2>
<p>To celebrate the new release, you can download PDF/EPUB versions of <strong>Understanding JavaScript RegExp</strong> for FREE till 05-Nov-2023. You can still pay if you wish ;)</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/js_regexp">Gumroad</a></li>
<li><a href="https://leanpub.com/js_regexp/c/new_js_regexp_release">Leanpub</a></li>
</ul>
<p><strong>All Books Bundle</strong> is just $12 (normal price $32) — includes all my 13 programming ebooks.</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/all-books/new_js_regexp_release">Gumroad</a></li>
<li><a href="https://leanpub.com/b/learnbyexample-all-books/c/new_js_regexp_release">Leanpub</a></li>
</ul>
<br>
<h2 id="what-s-new">What's new?<a class="zola-anchor" href="#what-s-new" aria-label="Anchor link for: what-s-new">🔗</a></h2>
<ul>
<li>Examples and exercises added for <code>d</code> and <code>v</code> flags</li>
<li>Strings in code snippets changed to be uniformly represented in single quotes</li>
<li>In general, many of the examples, exercises, solutions, descriptions and external links were updated/corrected</li>
<li>Updated Acknowledgements section</li>
<li>Code snippets related to info/warning sections will now appear as a single block</li>
<li>Book title changed to <strong>Understanding JavaScript RegExp</strong></li>
<li>New cover image</li>
<li>Images centered for EPUB format</li>
</ul>
<br>
<h2 id="videos">Videos<a class="zola-anchor" href="#videos" aria-label="Anchor link for: videos">🔗</a></h2>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/8X-hUel3GxM" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<p>On this blog, I <a href="https://learnbyexample.github.io/tips/">post tips</a> covering Python, command line tools and Vim. Here are video demos for these tips:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=THSMmCZQn1A&list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F">Python tips</a></li>
<li><a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY">Linux command line tips</a></li>
</ul>
<br>
<h2 id="testimonials">Testimonials<a class="zola-anchor" href="#testimonials" aria-label="Anchor link for: testimonials">🔗</a></h2>
<blockquote>
<p>Literally was having a mini-breakdown about not understanding Regex in algorithm solutions the other day and now I'm feeling so much better, so thank YOU! I genuinely feel like I'm developing the skill for spotting when and where to use them after so much practice!</p>
<p>— <a href="https://twitter.com/codingwithlucy/status/1450668315635036160">feedback on twitter</a></p>
</blockquote>
<br>
<h2 id="table-of-contents">Table of Contents<a class="zola-anchor" href="#table-of-contents" aria-label="Anchor link for: table-of-contents">🔗</a></h2>
<ol>
<li>Preface</li>
<li>Why is it needed?</li>
<li>RegExp introduction</li>
<li>Anchors</li>
<li>Alternation and Grouping</li>
<li>Escaping metacharacters</li>
<li>Dot metacharacter and Quantifiers</li>
<li>Interlude: Tools for debugging and visualization</li>
<li>Working with matched portions</li>
<li>Character class</li>
<li>Groupings and backreferences</li>
<li>Interlude: Common tasks</li>
<li>Lookarounds</li>
<li>Unicode</li>
<li>Further Reading</li>
</ol>
<br>
<h2 id="web-version">Web version<a class="zola-anchor" href="#web-version" aria-label="Anchor link for: web-version">🔗</a></h2>
<p>You can also read the book online here: <a href="https://learnbyexample.github.io/learn_js_regexp/">https://learnbyexample.github.io/learn_js_regexp/</a>.</p>
<br>
<h2 id="github-repo">GitHub repo<a class="zola-anchor" href="#github-repo" aria-label="Anchor link for: github-repo">🔗</a></h2>
<p>Visit <a href="https://github.com/learnbyexample/learn_js_regexp">https://github.com/learnbyexample/learn_js_regexp</a> for markdown source, exercise solutions, sample chapters and other details related to the book.</p>
<p>See <a href="https://learnbyexample.github.io/customizing-pandoc/">my blog post</a> on how to customize <code>pandoc</code> for generating beautiful PDF/EPUB versions from GitHub style markdown.</p>
<br>
<h2 id="newsletter">Newsletter<a class="zola-anchor" href="#newsletter" aria-label="Anchor link for: newsletter">🔗</a></h2>
<p>Subscribe to <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> — free newsletter covering programming resources, updates on what I am creating, tools, free ebooks and more, delivered every Friday.</p>
<br>
<h2 id="feedback-and-errata">Feedback and Errata<a class="zola-anchor" href="#feedback-and-errata" aria-label="Anchor link for: feedback-and-errata">🔗</a></h2>
<p>I would highly appreciate if you'd <strong>let me know how you felt about this book</strong>. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.</p>
<p>You can reach me via:</p>
<ul>
<li>Issue Manager: <a href="https://github.com/learnbyexample/learn_js_regexp/issues">https://github.com/learnbyexample/learn_js_regexp/issues</a></li>
<li>E-mail: <code>learn by [email protected]</code> (without the spaces)</li>
<li>Twitter: <a href="https://twitter.com/learn_byexample">https://twitter.com/learn_byexample</a></li>
</ul>
<p>Happy learning :)</p>
CLI text editing with ed2023-10-17T00:00:00+00:002024-03-28T00:00:00+00:00https://learnbyexample.github.io/mini/cli-text-editing-with-ed/<p>I'm finally writing a post on the <code>ed</code> command. And I'm keeping it short so that I'll actually publish the post. The examples presented below will be easier to understand for those already familiar with Vim and <code>sed</code>. See the links at the end for learning resources.</p>
<p>Although I'm interested in getting to know <code>ed</code> better, I don't really find myself in situations where it'd help me. But, I have used it a few times to answer questions on stackoverflow.</p>
<h2 id="moving-lines">Moving lines</h2>
<p>Consider this sample input file:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat ip.txt
</span><span>apple
</span><span>banana
</span><span>cherry
</span><span>fig
</span><span>mango
</span><span>pineapple
</span></code></pre>
<p>Suppose, you want to move the third line to the top. If you are using Vim, you can execute <code>:3m0</code> where <code>3</code> is the input address, <code>m</code> is the <em>move</em> command and <code>0</code> is the target address. To do the same with <code>ed</code>:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'3m0\nwq\n' </span><span style="color:#72ab00;">|</span><span> ed </span><span style="color:#72ab00;">-</span><span>s ip.txt </span><span style="color:#72ab00;">-
</span><span>
</span><span>$ cat ip.txt
</span><span>cherry
</span><span>apple
</span><span>banana
</span><span>fig
</span><span>mango
</span><span>pineapple
</span></code></pre>
<p>The <code>3m0</code> part in the above <code>ed</code> command is identical to the Vim solution. After that, another command <code>wq</code> (write and quit) is issued to save the changes (again, Vim users would be familiar with this combination). The <code>-s</code> option suppresses diagnostics and other details. <code>-</code> is used to indicate that the <code>ed</code> script is passed via stdin.</p>
<p>You can also move lines based on a regexp match. Here's an example:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># move the first matching line containing 'an' to the top of the file
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'/an/m0\nwq\n' </span><span style="color:#72ab00;">|</span><span> ed </span><span style="color:#72ab00;">-</span><span>s ip.txt </span><span style="color:#72ab00;">-
</span><span>
</span><span>$ cat ip.txt
</span><span>banana
</span><span>cherry
</span><span>apple
</span><span>fig
</span><span>mango
</span><span>pineapple
</span></code></pre>
<p>If you want to move all the matching lines, you can use the <code>g</code> command (same as Vim). Note that the first matching line will be moved first, then the next matching line and so on. So the order will be reversed after the move.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'g/app/m0\nwq\n' </span><span style="color:#72ab00;">|</span><span> ed </span><span style="color:#72ab00;">-</span><span>s ip.txt </span><span style="color:#72ab00;">-
</span><span>
</span><span>$ cat ip.txt
</span><span>pineapple
</span><span>apple
</span><span>banana
</span><span>cherry
</span><span>fig
</span><span>mango
</span></code></pre>
<p>Here's the <a href="https://stackoverflow.com/q/67031062/4082052">stackoverflow link</a> that inspired the above examples. See <a href="https://stackoverflow.com/a/75222984/4082052">this stackoverflow answer</a> for more examples of moving lines. See <a href="https://stackoverflow.com/a/48840851/4082052">this one</a> to learn how to copy a particular line to the end of the file. See <a href="https://unix.stackexchange.com/a/759710/109046">this unix.stackexchange answer</a> for an example of moving a range of lines, where the same regex matches both the starting and ending lines.</p>
<h2 id="negative-addressing">Negative addressing</h2>
<p>There are plenty of <a href="https://learnbyexample.github.io/learn_gnused/selective-editing.html">addressing features</a> provided by the <code>GNU sed</code> command, but negative addressing isn't one. Here's an example of deleting the last but second line using <code>ed</code>:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat colors.txt
</span><span>red
</span><span>green
</span><span>blue
</span><span>yellow
</span><span>black
</span><span>
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'$-2d\nwq\n' </span><span style="color:#72ab00;">|</span><span> ed </span><span style="color:#72ab00;">-</span><span>s colors.txt </span><span style="color:#72ab00;">-
</span><span>$ cat colors.txt
</span><span>red
</span><span>green
</span><span>yellow
</span><span>black
</span></code></pre>
<h2 id="resource-links">Resource links</h2>
<ul>
<li><a href="https://blog.sanctum.geek.nz/actually-using-ed/">Actually using ed</a></li>
<li><a href="https://raymii.org/s/tutorials/ed_cheatsheet.html">ed cheatsheet</a></li>
<li><a href="https://jvns.ca/blog/2018/05/11/batch-editing-files-with-ed/">Batch editing files with ed</a></li>
<li><a href="https://en.wikipedia.org/wiki/Ed_(text_editor)">wikipedia entry for the ed command</a></li>
</ul>
Perl One-Liners Guide book announcement2023-09-28T00:00:00+00:002023-10-04T00:00:00+00:00https://learnbyexample.github.io/perl-oneliners-guide-announcement/<p>Hello!</p>
<p>I am pleased to announce a new version of my <strong>Perl One-Liners Guide</strong> ebook. Examples, exercises, solutions, descriptions and external links were added/updated/corrected.</p>
<p>When it comes to command line text processing, there are several well known tools like <code>grep</code> for filtering, <code>sed</code> for substitution and <code>awk</code> for field processing. Compared to such tools, <strong>Perl has a feature rich regular expression engine, plenty of builtin modules and a thriving ecosystem</strong>. Another advantage is that Perl is more <strong>portable</strong>.</p>
<p>This ebook will show examples for filtering and substitution features, field processing, using standard and third-party modules, multiple file processing, how to construct solutions that depend on multiple records, how to compare records and fields between two or more files, how to identify duplicates while maintaining input order and so on.</p>
<span id="continue-reading"></span><br>
<h2 id="release-offers">Release offers<a class="zola-anchor" href="#release-offers" aria-label="Anchor link for: release-offers">🔗</a></h2>
<p>To celebrate the new release, you can download PDF/EPUB versions of <strong>Perl One-Liners Guide</strong> for FREE till 07-October-2023. You can still pay if you wish ;)</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/perl-oneliners">Gumroad</a></li>
<li><a href="https://leanpub.com/perl-oneliners/c/new_perl_release">Leanpub</a></li>
</ul>
<p><strong>All Books Bundle</strong> is just $12 (normal price $32), includes all my 13 programming ebooks.</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/all-books/new_perl_release">Gumroad</a></li>
<li><a href="https://leanpub.com/b/learnbyexample-all-books/c/new_perl_release">Leanpub</a></li>
</ul>
<br>
<h2 id="what-s-new">What's new?<a class="zola-anchor" href="#what-s-new" aria-label="Anchor link for: what-s-new">🔗</a></h2>
<ul>
<li>Command version updated to <strong>Perl 5.38.0</strong>
<ul>
<li>option <code>-g</code> slurps entire file contents</li>
</ul>
</li>
<li>Many more exercises added</li>
<li>Long sections split into smaller ones</li>
<li>In general, many of the examples, exercises, solutions, descriptions and external links were updated/corrected</li>
<li>Updated Acknowledgements section</li>
<li>Code snippets related to info/warning sections will now appear as a single block</li>
<li>Book title changed to <strong>Perl One-Liners Guide</strong></li>
<li>New cover image</li>
</ul>
<br>
<h2 id="videos">Videos<a class="zola-anchor" href="#videos" aria-label="Anchor link for: videos">🔗</a></h2>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/_7tP_4T45Ok" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<p>On this blog, I <a href="https://learnbyexample.github.io/tips/">post tips</a> covering Python, command line tools and Vim. Here are video demos for these tips:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=THSMmCZQn1A&list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F">Python tips</a></li>
<li><a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY">Linux command line tips</a></li>
</ul>
<br>
<h2 id="testimonials">Testimonials<a class="zola-anchor" href="#testimonials" aria-label="Anchor link for: testimonials">🔗</a></h2>
<blockquote>
<p>This is fantastic! 👏 I use Perl one-liners for record and text processing a lot and this will be definitely something I will keep coming back to - I’ve already learned a trick from “Context Matching” (9) 🙂</p>
<p>— <a href="https://programming.dev/comment/3277968">feedback on [email protected]</a></p>
</blockquote>
<br>
<h2 id="table-of-contents">Table of Contents<a class="zola-anchor" href="#table-of-contents" aria-label="Anchor link for: table-of-contents">🔗</a></h2>
<ol>
<li>Preface</li>
<li>One-liner introduction</li>
<li>Line processing</li>
<li>In-place file editing</li>
<li>Field separators</li>
<li>Record separators</li>
<li>Using modules</li>
<li>Multiple file input</li>
<li>Processing multiple records</li>
<li>Two file processing</li>
<li>Dealing with duplicates</li>
<li>Perl rename command</li>
</ol>
<br>
<h2 id="web-version">Web version<a class="zola-anchor" href="#web-version" aria-label="Anchor link for: web-version">🔗</a></h2>
<p>You can also read the book online here: <a href="https://learnbyexample.github.io/learn_perl_oneliners/">https://learnbyexample.github.io/learn_perl_oneliners/</a>.</p>
<br>
<h2 id="github-repo">GitHub repo<a class="zola-anchor" href="#github-repo" aria-label="Anchor link for: github-repo">🔗</a></h2>
<p>Visit <a href="https://github.com/learnbyexample/learn_perl_oneliners">https://github.com/learnbyexample/learn_perl_oneliners</a> for markdown source, example files, exercise solutions, sample chapters and other details related to the book.</p>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://learnbyexample.github.io/customizing-pandoc/">my blog post</a> on how to customize <code>pandoc</code> for generating beautiful PDF/EPUB versions from GitHub style markdown.</p>
</blockquote>
<br>
<h2 id="newsletter">Newsletter<a class="zola-anchor" href="#newsletter" aria-label="Anchor link for: newsletter">🔗</a></h2>
<p>Subscribe to <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> — free newsletter covering programming resources, updates on what I am creating, tips, tools, free ebooks and more, delivered every Friday.</p>
<br>
<h2 id="feedback-and-errata">Feedback and Errata<a class="zola-anchor" href="#feedback-and-errata" aria-label="Anchor link for: feedback-and-errata">🔗</a></h2>
<p>I would highly appreciate it if you'd <strong>let me know how you felt about this book</strong>. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.</p>
<p>You can reach me via:</p>
<ul>
<li>Issue Manager: <a href="https://github.com/learnbyexample/learn_perl_oneliners/issues">https://github.com/learnbyexample/learn_perl_oneliners/issues</a></li>
<li>E-mail: <code>echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode</code></li>
<li>Twitter: <a href="https://twitter.com/learn_byexample">https://twitter.com/learn_byexample</a></li>
</ul>
<p>Happy learning :)</p>
Vim tip 33: editing with text objects2023-09-25T00:00:00+00:002023-09-25T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-33/<p>Combining motions such as <code>w</code>, <code>%</code> and <code>f</code> with editing commands like <code>d</code>, <code>c</code> and <code>y</code> require precise positioning to be effective.</p>
<p>Vim also provides a list of handy context based options to make certain editing use cases easier using the <code>i</code> and <code>a</code> text object selections. You can easily remember the difference between these two options by thinking <code>i</code> as <strong>inner</strong> and <code>a</code> as <strong>around</strong>.</p>
<ul>
<li><kbd>diw</kbd> delete a word regardless of where the cursor is on that word
<ul>
<li>equivalent to using <kbd>de</kbd> when the cursor is on the first character of the word</li>
</ul>
</li>
<li><kbd>diW</kbd> delete a WORD regardless of where the cursor is on that WORD</li>
<li><kbd>daw</kbd> delete a word regardless of where the cursor is on that word as well as a space character to the left/right of the word depending on its position in the current sentence</li>
<li><kbd>dis</kbd> delete a sentence regardless of where the cursor is on that sentence</li>
<li><kbd>yas</kbd> copy a sentence regardless of where the cursor is on that sentence as well as a space character to the left/right</li>
<li><kbd>cip</kbd> delete a paragraph regardless of where the cursor is on that paragraph and change to Insert mode</li>
<li><kbd>dit</kbd> delete all characters within HTML/XML tags, nesting is taken care as well
<ul>
<li>see <a href="https://vimhelp.org/motion.txt.html#tag-blocks">:h tag-blocks</a> for details about corner cases</li>
</ul>
</li>
<li><kbd>di"</kbd> delete all characters within a pair of double quotes, regardless of where the cursor is within the quotes</li>
<li><kbd>da'</kbd> delete all characters within a pair of single quotes along with the quote characters</li>
<li><kbd>ci(</kbd> delete all characters within <code>()</code> and change to Insert mode
<ul>
<li>works even if the parenthesis are spread over multiple lines, nesting is taken care as well</li>
</ul>
</li>
<li><kbd>ya}</kbd> copy all characters within <code>{}</code> including the <code>{}</code> characters
<ul>
<li>works even if the braces are spread over multiple lines, nesting is taken care as well</li>
</ul>
</li>
</ul>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> You can use a count prefix for nested cases. For example, <kbd>c2i{</kbd> will clear the inner braces (including the braces, and this could be nested too) and then only the text between braces for the next level.</p>
</blockquote>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/motion.txt.html#text-objects">:h text-objects</a> for more details.</p>
</blockquote>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/jbZMDQcwnV4" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
Vim tip 32: text and indent settings2023-09-19T00:00:00+00:002023-09-19T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-32/<p>Here are some text and indent Vim settings that you can put in the <code>vimrc</code> file to customize your editor. See <a href="https://vimhelp.org/options.txt.html">:h options.txt</a> for complete reference.</p>
<ul>
<li><kbd>filetype plugin indent on</kbd> enables loading of <code>plugin</code> and <code>indent</code> files
<ul>
<li>these files become active based on the type of the file to influence syntax highlighting, indentation, etc</li>
<li><kbd>:echo $VIMRUNTIME</kbd> gives your installation directory (<code>indent</code> and <code>plugin</code> directories would be present in this path)</li>
<li>see <a href="https://vimhelp.org/usr_05.txt.html#vimrc-filetype">:h vimrc-filetype</a>, <a href="https://vimhelp.org/filetype.txt.html#%3Afiletype-overview">:h :filetype-overview</a> and <a href="https://vimhelp.org/filetype.txt.html">:h filetype.txt</a> for more details</li>
</ul>
</li>
<li><kbd>set autoindent</kbd> copy indent from the current line when starting a new line
<ul>
<li>useful for files not affected by <code>indent</code> setting</li>
<li>see also <a href="https://vimhelp.org/options.txt.html#%27smartindent%27">:h smartindent</a></li>
</ul>
</li>
<li><kbd>set textwidth=80</kbd> guideline for Vim to automatically move to a new line with <code>80</code> characters as the limit
<ul>
<li>white space is used to break lines, so a line can still be greater than the limit if there's no white space</li>
<li>default is <code>0</code> which disables this setting</li>
</ul>
</li>
<li><kbd>set colorcolumn=80</kbd> create a highlighted vertical bar at column number <code>80</code>
<ul>
<li>use <code>highlight ColorColumn</code> setting to customize the color for this vertical bar</li>
<li>see <a href="https://vi.stackexchange.com/q/574/1616">vi.stackexchange: Keeping lines to less than 80 characters</a> for more details</li>
</ul>
</li>
<li><kbd>set shiftwidth=4</kbd> number of spaces to use for indentation (default is <code>8</code>)</li>
<li><kbd>set tabstop=4</kbd> width for the tab character (default is <code>8</code>)</li>
<li><kbd>set expandtab</kbd> use spaces for tab expansion</li>
<li><kbd>set cursorline</kbd> highlight the line containing the cursor</li>
</ul>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/3F5R8BDuMGc" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 33: manipulating string case with GNU sed2023-09-11T00:00:00+00:002023-09-11T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-33/<p><code>sed</code> provides escape sequences to change the case of replacement strings, which might include backreferences, shell variables, etc.</p>
<table><thead><tr><th>Sequence</th><th>Description</th></tr></thead><tbody>
<tr><td><code>\E</code></td><td>indicates the end of case conversion</td></tr>
<tr><td><code>\l</code></td><td>convert the next character to lowercase</td></tr>
<tr><td><code>\u</code></td><td>convert the next character to uppercase</td></tr>
<tr><td><code>\L</code></td><td>convert the following characters to lowercase (overridden by <code>\U</code> or <code>\E</code>)</td></tr>
<tr><td><code>\U</code></td><td>convert the following characters to uppercase (overridden by <code>\L</code> or <code>\E</code>)</td></tr>
</tbody></table>
<p>First up, changing case of only the immediate next character after the escape sequence.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># match only the first character of a word
</span><span style="color:#7f8989;"># use & to backreference the matched character
</span><span style="color:#7f8989;"># \u would then change it to uppercase
</span><span>$ echo </span><span style="color:#d07711;">'hello there. how are you?' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/\b\w/\u&/g'
</span><span style="color:#5597d6;">Hello There</span><span>. </span><span style="color:#5597d6;">How Are You</span><span style="color:#72ab00;">?
</span><span>
</span><span style="color:#7f8989;"># change the first character of a word to lowercase
</span><span>$ echo </span><span style="color:#d07711;">'HELLO THERE. HOW ARE YOU?' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/\b\w/\l&/g'
</span><span>hELLO tHERE. hOW aRE yOU?
</span><span>
</span><span style="color:#7f8989;"># match lowercase followed by underscore followed by lowercase
</span><span style="color:#7f8989;"># delete the underscore and convert the 2nd lowercase to uppercase
</span><span>$ echo </span><span style="color:#d07711;">'_fig aug_price next_line' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/([a-z])_([a-z])/\1\u\2/g'
</span><span>_fig augPrice nextLine
</span></code></pre>
<p>Next, changing case of multiple characters at a time.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># change all alphabets to lowercase
</span><span>$ echo </span><span style="color:#d07711;">'HaVE a nICe dAy' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/.*/\L&/'
</span><span>have a nice day
</span><span style="color:#7f8989;"># change all alphabets to uppercase
</span><span>$ echo </span><span style="color:#d07711;">'HaVE a nICe dAy' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/.*/\U&/'
</span><span style="color:#5597d6;">HAVE A NICE DAY
</span><span>
</span><span style="color:#7f8989;"># \E will stop further conversion
</span><span>$ echo </span><span style="color:#d07711;">'fig_ aug_price next_line' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/([a-z]+)(_[a-z]+)/\U\1\E\2/g'
</span><span>fig_ </span><span style="color:#5597d6;">AUG_price NEXT_line
</span><span style="color:#7f8989;"># \L or \U will override any existing conversion
</span><span>$ echo </span><span style="color:#d07711;">'HeLLo:bYe gOoD:beTTEr' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/([a-z]+)(:[a-z]+)/\L\1\U\2/Ig'
</span><span style="color:#b3933a;">hello:</span><span style="color:#5597d6;">BYE </span><span style="color:#b3933a;">good:</span><span style="color:#5597d6;">BETTER
</span></code></pre>
<p>Finally, examples where escapes are used next to each other.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># uppercase first character of a word
</span><span style="color:#7f8989;"># and lowercase rest of the word characters
</span><span style="color:#7f8989;"># note the order of escapes used, \u\L won't work
</span><span>$ echo </span><span style="color:#d07711;">'HeLLo:bYe gOoD:beTTEr' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/[a-z]+/\L\u&/Ig'
</span><span style="color:#b3933a;">Hello:</span><span style="color:#5597d6;">Bye </span><span style="color:#b3933a;">Good:</span><span style="color:#5597d6;">Better
</span><span>
</span><span style="color:#7f8989;"># lowercase first character of a word
</span><span style="color:#7f8989;"># and uppercase rest of the word characters
</span><span>$ echo </span><span style="color:#d07711;">'HeLLo:bYe gOoD:beTTEr' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/[a-z]+/\U\l&/Ig'
</span><span style="color:#b3933a;">hELLO:</span><span>bYE </span><span style="color:#b3933a;">gOOD:</span><span>bETTER
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/h5-JoNvvihY" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/learn_gnused">CLI text processing with GNU sed</a> ebook.</p>
Python tip 33: sorting iterables based on multiple conditions2023-09-05T00:00:00+00:002023-09-05T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-33/<p>In an <a href="https://learnbyexample.github.io/tips/python-tip-21/">earlier tip</a>, you learned how to sort iterables based on a key. You can use a sequence like <code>list</code> or <code>tuple</code> to specify a tie-breaker condition when two or more items are deemed equal under the primary sorting rule.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>books </span><span style="color:#72ab00;">= </span><span>(</span><span style="color:#d07711;">'Mage Errant'</span><span>, </span><span style="color:#d07711;">'Piranesi'</span><span>, </span><span style="color:#d07711;">'Cradle'</span><span>, </span><span style="color:#d07711;">'The Weirkey Chronicles'</span><span>, </span><span style="color:#d07711;">'Mistborn'</span><span>)
</span><span>
</span><span style="color:#7f8989;"># sorts based on the number of words
</span><span style="color:#7f8989;"># retains original order for items with the same number of words
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">sorted</span><span>(books, </span><span style="color:#5597d6;">key</span><span style="color:#72ab00;">=lambda </span><span style="color:#5597d6;">b</span><span>: b.</span><span style="color:#5597d6;">count</span><span>(</span><span style="color:#d07711;">' '</span><span>))
</span><span>[</span><span style="color:#d07711;">'Piranesi'</span><span>, </span><span style="color:#d07711;">'Cradle'</span><span>, </span><span style="color:#d07711;">'Mistborn'</span><span>, </span><span style="color:#d07711;">'Mage Errant'</span><span>, </span><span style="color:#d07711;">'The Weirkey Chronicles'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># items with the same number of words are further sorted in alphabetic order
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">sorted</span><span>(books, </span><span style="color:#5597d6;">key</span><span style="color:#72ab00;">=lambda </span><span style="color:#5597d6;">b</span><span>: (b.</span><span style="color:#5597d6;">count</span><span>(</span><span style="color:#d07711;">' '</span><span>), b))
</span><span>[</span><span style="color:#d07711;">'Cradle'</span><span>, </span><span style="color:#d07711;">'Mistborn'</span><span>, </span><span style="color:#d07711;">'Piranesi'</span><span>, </span><span style="color:#d07711;">'Mage Errant'</span><span>, </span><span style="color:#d07711;">'The Weirkey Chronicles'</span><span>]
</span></code></pre>
<p>To sort in descending order, usually the <code>reverse=True</code> keyword argument is used. But what if the primary and secondary rules are opposites? If one of the rule is numerical in nature, you can simply negate the number to reverse the order.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># descending order based on the number of words
</span><span style="color:#7f8989;"># ascending alphabetic order for items with the same number of words
</span><span style="color:#72ab00;">>>></span><span> sorted(</span><span style="color:#5597d6;">books,</span><span> key=lambda b: (-b.count(</span><span style="color:#d07711;">' '</span><span>), b))
</span><span style="color:#5597d6;">[</span><span style="color:#d07711;">'The Weirkey Chronicles'</span><span style="color:#5597d6;">, </span><span style="color:#d07711;">'Mage Errant'</span><span>, </span><span style="color:#d07711;">'Cradle'</span><span>, </span><span style="color:#d07711;">'Mistborn'</span><span>, </span><span style="color:#d07711;">'Piranesi'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># reverse the above result
</span><span style="color:#72ab00;">>>></span><span> sorted(</span><span style="color:#5597d6;">books,</span><span> key=lambda b: (-b.count(</span><span style="color:#d07711;">' '</span><span>), b)</span><span style="color:#5597d6;">,</span><span> reverse=True)
</span><span style="color:#5597d6;">[</span><span style="color:#d07711;">'Piranesi'</span><span style="color:#5597d6;">, </span><span style="color:#d07711;">'Mistborn'</span><span>, </span><span style="color:#d07711;">'Cradle'</span><span>, </span><span style="color:#d07711;">'Mage Errant'</span><span>, </span><span style="color:#d07711;">'The Weirkey Chronicles'</span><span>]
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://docs.python.org/3/howto/sorting.html">docs.python HOWTOs: Sorting</a>.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/YRLs92qWa9c" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 31: mark frequently used locations2023-08-29T00:00:00+00:002023-08-29T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-31/<p>You can save frequently visited locations using marks for quicker navigation to those positions in the file. You can also pair marks with motion commands for tasks like copying, deleting, etc.</p>
<ul>
<li><kbd>ma</kbd> mark location in the file using the alphabet <code>a</code>
<ul>
<li>you can use any of the 26 alphabets</li>
<li>use lowercase alphabets to work within the current file</li>
<li>use uppercase alphabets to work from any file</li>
<li><kbd>:marks</kbd> will show a list of the existing marks</li>
</ul>
</li>
<li><kbd>`a</kbd> move to the exact location marked by <code>a</code></li>
<li><kbd>'a</kbd> move to the first non-blank character of the line marked by <code>a</code></li>
<li><kbd>'A</kbd> move to the first non-blank character of the line marked by <code>A</code> (this will work for any file where the mark was set)</li>
<li><kbd>d`a</kbd> delete from the current character to the character marked by <code>a</code>
<ul>
<li>marks can be paired with any command that accept motions like <code>d</code>, <code>y</code>, <code>></code>, etc</li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> Motion commands that take you across lines (for example, <kbd>10G</kbd>) will automatically save the location you jumped from in the default <code>`</code> mark. You can move back to that exact location using <code>``</code> or the first non-blank character using <code>'`</code>. Note that the arrow and word motions aren't considered for the default mark even if they move across lines.</p>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/motion.txt.html#mark-motions">:h mark-motions</a> for more ways to use marks.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/mTed-8UxNBA" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 32: text processing between two files with GNU awk2023-08-21T00:00:00+00:002023-08-21T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-32/<p><code>awk</code> is handy to compare records and fields between two or more files. The <em>key</em> features used in the solution below:</p>
<ul>
<li>For two files as input, <code>NR==FNR</code> will be <code>true</code> only when the first file is being processed</li>
<li><code>next</code> will skip rest of the script and fetch the next record</li>
<li><code>a[$0]</code> by itself is a valid statement. It will create an uninitialized element in array <code>a</code> with <code>$0</code> as the key (assuming the key doesn't exist yet)</li>
<li><code>$0 in a</code> checks if the given string (<code>$0</code> here) exists as a key in the array <code>a</code></li>
</ul>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat colors_1.txt
</span><span>teal
</span><span>light blue
</span><span>green
</span><span>yellow
</span><span>$ cat colors_2.txt
</span><span>light blue
</span><span>black
</span><span>dark green
</span><span>yellow
</span><span>
</span><span style="color:#7f8989;"># common lines
</span><span>$ awk </span><span style="color:#d07711;">'NR==FNR{a[$0]; next} $0 in a'</span><span> colors_1.txt colors_2.txt
</span><span>light blue
</span><span>yellow
</span><span>
</span><span style="color:#7f8989;"># lines from colors_2.txt not present in colors_1.txt
</span><span>$ awk </span><span style="color:#d07711;">'NR==FNR{a[$0]; next} !($0 in a)'</span><span> colors_1.txt colors_2.txt
</span><span>black
</span><span>dark green
</span></code></pre>
<blockquote>
<p><img src="/images/warning.svg" alt="warning" /> Note that the <code>NR==FNR</code> logic will fail if the first file is empty, since <code>NR</code> wouldn't get a chance to increment. You can set a flag after the first file has been processed to avoid this issue. See <a href="https://unix.stackexchange.com/a/237110/109046">this unix.stackexchange thread</a> for more workarounds.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># no output
</span><span>$ awk </span><span style="color:#d07711;">'NR==FNR{a[$0]; next} !($0 in a)' </span><span style="color:#72ab00;">/</span><span>dev</span><span style="color:#72ab00;">/</span><span>null </span><span style="color:#72ab00;"><</span><span>(seq </span><span style="color:#b3933a;">2</span><span>)
</span><span>
</span><span style="color:#7f8989;"># gives the expected output
</span><span>$ awk </span><span style="color:#d07711;">'!f{a[$0]; next} !($0 in a)' </span><span style="color:#72ab00;">/</span><span>dev</span><span style="color:#72ab00;">/</span><span>null f=</span><span style="color:#b3933a;">1 </span><span style="color:#72ab00;"><</span><span>(seq </span><span style="color:#b3933a;">2</span><span>)
</span><span style="color:#b3933a;">1
</span><span style="color:#b3933a;">2
</span></code></pre>
</blockquote>
<p>Here's an example of comparing specific fields instead of whole lines. When you use a <code>,</code> separator between strings to construct the array key, the value of <code>SUBSEP</code> is inserted. This special variable has a default value of the non-printing character <code>\034</code> which is usually not used as part of text files.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat marks.txt
</span><span style="color:#5597d6;">Dept Name Marks
</span><span style="color:#5597d6;">ECE Raj </span><span style="color:#b3933a;">53
</span><span style="color:#5597d6;">ECE Joel </span><span style="color:#b3933a;">72
</span><span style="color:#5597d6;">EEE Moi </span><span style="color:#b3933a;">68
</span><span style="color:#5597d6;">CSE Surya </span><span style="color:#b3933a;">81
</span><span style="color:#5597d6;">EEE Tia </span><span style="color:#b3933a;">59
</span><span style="color:#5597d6;">ECE Om </span><span style="color:#b3933a;">92
</span><span style="color:#5597d6;">CSE Amy </span><span style="color:#b3933a;">67
</span><span>
</span><span>$ cat dept_name.txt
</span><span style="color:#5597d6;">EEE Moi
</span><span style="color:#5597d6;">CSE Amy
</span><span style="color:#5597d6;">ECE Raj
</span><span>
</span><span>$ awk </span><span style="color:#d07711;">'NR==FNR{a[$1,$2]; next} ($1,$2) in a'</span><span> dept_name.txt marks.txt
</span><span style="color:#5597d6;">ECE Raj </span><span style="color:#b3933a;">53
</span><span style="color:#5597d6;">EEE Moi </span><span style="color:#b3933a;">68
</span><span style="color:#5597d6;">CSE Amy </span><span style="color:#b3933a;">67
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/gz-0UQGUfNA" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/learn_gnuawk">CLI text processing with GNU awk</a> ebook.</p>
Interactive exercises for GNU grep, sed and awk (TUI apps)2023-08-17T00:00:00+00:002025-01-09T00:00:00+00:00https://learnbyexample.github.io/interactive-grep-sed-awk-exercises/<p>Having an interactive program that automatically loads questions and checks the solution is immensely helpful to have while learning a topic. I've written <a href="https://github.com/learnbyexample/TUI-apps">TUI apps</a> with plenty of beginner to intermediate level exercises for <code>GNU grep</code>, <code>GNU sed</code> and <code>GNU awk</code>.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/GrepExercises/grep_exercises.png" alt="Sample screenshot for GNU grep exercises" loading="lazy" /></p>
<span id="continue-reading"></span><br>
<h2 id="installation">Installation<a class="zola-anchor" href="#installation" aria-label="Anchor link for: installation">🔗</a></h2>
<p>For the past few months, I've been using a Python framework called <a href="https://textual.textualize.io/">Textual</a> to create interactive TUI apps.</p>
<p>You'll need Python for this. This app is available on PyPI as <a href="https://pypi.org/project/grepexercises/">grepexercises</a>, <a href="https://pypi.org/project/sedexercises/">sedexercises</a> and <a href="https://pypi.org/project/awkexercises/">awkexercises</a>. Example installation instructions are shown below, adjust them based on your preferences and OS.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># virtual environment
</span><span style="color:#5597d6;">$</span><span> python3</span><span style="color:#5597d6;"> -m</span><span> venv textual_apps
</span><span style="color:#5597d6;">$</span><span> cd textual_apps
</span><span style="color:#5597d6;">$</span><span> source bin/activate
</span><span style="color:#5597d6;">$</span><span> pip install grepexercises sedexercises awkexercises
</span><span>
</span><span style="color:#7f8989;"># launch the app, example shown for the grep command
</span><span style="color:#5597d6;">$</span><span> grepexercises
</span></code></pre>
<p>To run the app without having to enter the virtual environment again, add aliases to <code>.bashrc</code> (or equivalent):</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># you'll have to change the path
</span><span style="color:#b39f04;">alias </span><span style="color:#c23f31;">grepexercises</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'/path/to/textual_apps/bin/grepexercises'
</span><span>
</span><span style="color:#7f8989;"># similarly, you can add aliases for the other apps as well
</span></code></pre>
<p>As an alternative to manually managing such virtual environments, you can use <a href="https://github.com/pypa/pipx">https://github.com/pypa/pipx</a> instead:</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> pipx install grepexercises sedexercises awkexercises
</span><span style="color:#5597d6;">$</span><span> awkexercises
</span></code></pre>
<p>As yet another alternative, you can install <code>textual==0.85.2</code> (see <a href="https://textual.textualize.io/getting_started/">Textual documentation</a> for more details), clone my <a href="https://github.com/learnbyexample/TUI-apps">TUI-apps repository</a> and run the Python file from respective folders. For example, <code>grep_exercises.py</code> for the <code>grep</code> command.</p>
<p>Adjust the terminal dimensions for the widgets to appear properly, for example 84x25 (characters x lines).</p>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> You can use alternative CLI tools to solve these exercises as well. For example, <code>perl</code> instead of <code>GNU awk</code> or <code>ripgrep</code> instead of <code>GNU grep</code> and so on.</p>
</blockquote>
<br>
<h2 id="brief-guide">Brief Guide<a class="zola-anchor" href="#brief-guide" aria-label="Anchor link for: brief-guide">🔗</a></h2>
<p>You can either click the buttons using mouse or press the key combinations listed below:</p>
<ul>
<li>Press <strong>F1</strong> to view the complete guide from within the app itself.</li>
<li>Press <strong>Ctrl+p</strong> and <strong>Ctrl+n</strong> to navigate the questions list.</li>
<li>Type the command in the box below the question.</li>
<li>Press <strong>Enter</strong> to execute the command.
<ul>
<li>Output would be displayed below the command box.</li>
<li>If the output matches the expected results, the command box will turn <em>green</em> and reference solutions will also be shown.</li>
<li>Issues due to errors and timeout (about <code>2</code> seconds) will be displayed in <em>red</em>.</li>
</ul>
</li>
<li>Press <strong>Ctrl+s</strong> to toggle the reference solution box.</li>
<li>Press <strong>Ctrl+t</strong> to toggle between light and dark themes.</li>
<li>Press <strong>Ctrl+q</strong> to quit the app.</li>
<li>Some basic readline-like shortcuts are supported, for example <strong>Ctrl+u</strong>, <strong>Ctrl+k</strong>, <strong>Ctrl+w</strong>, etc</li>
</ul>
<p>Your progress is automatically saved when you close the app and restored when you launch it again later. Already answered questions will be skipped.</p>
<blockquote>
<p><img src="/images/warning.svg" alt="warning" /> There is no safeguard against the command you are executing. They are treated as if you typed them from a shell session.</p>
</blockquote>
<br>
<h2 id="ebooks">Ebooks<a class="zola-anchor" href="#ebooks" aria-label="Anchor link for: ebooks">🔗</a></h2>
<p>The exercise questions in these apps have been adapted from my programming ebooks: <a href="https://learnbyexample.github.io/books/">https://learnbyexample.github.io/books/</a></p>
<br>
<h2 id="feedback">Feedback<a class="zola-anchor" href="#feedback" aria-label="Anchor link for: feedback">🔗</a></h2>
<p>I'd highly appreciate your feedback. Please file <a href="https://github.com/learnbyexample/TUI-apps/issues">an issue</a> if there are bugs, crashes, etc.</p>
<p>Hope you find these TUI apps useful. Happy learning :)</p>
Python tip 32: positive lookarounds2023-08-16T00:00:00+00:002023-08-16T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-32/<p>Lookarounds help to create custom anchors and add conditions within a regex definition. These assertions are also known as <strong>zero-width patterns</strong> because they add restrictions similar to anchors and are not part of the matched portions. Negative lookarounds were discussed in <a href="https://learnbyexample.github.io/tips/python-tip-29/">this post</a>. The syntax for positive lookarounds is shown below:</p>
<ul>
<li><code>(?=pat)</code> positive lookahead assertion</li>
<li><code>(?<=pat)</code> positive lookbehind assertion</li>
</ul>
<p>Here are some examples:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>s </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'42 apple-5, fig3; x-83, y-20: f12'
</span><span>
</span><span style="color:#7f8989;"># extract digits only if it is followed by ,
</span><span style="color:#7f8989;"># note that end of string doesn't qualify as this is a positive assertion
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?=</span><span style="color:#7c8f4c;">,)</span><span style="color:#d07711;">'</span><span>, s)
</span><span>[</span><span style="color:#d07711;">'5'</span><span>, </span><span style="color:#d07711;">'83'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># extract digits only if it is preceded by - and followed by ; or :
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?<=</span><span style="color:#7c8f4c;">-)</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?=[:;]</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, s)
</span><span>[</span><span style="color:#d07711;">'20'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># replace 'par' as long as 'part' occurs as a whole word later in the line
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">par(</span><span style="color:#aeb52b;">?=.</span><span style="color:#72ab00;">*\b</span><span style="color:#7c8f4c;">part</span><span style="color:#72ab00;">\b</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'[</span><span style="text-decoration:underline;font-style:italic;color:#d2a8a1;">\g</span><span style="color:#d07711;"><0>]'</span><span>, </span><span style="color:#d07711;">'par spare part party'</span><span>)
</span><span style="color:#d07711;">'[par] s[par]e part party'
</span></code></pre>
<p>With lookbehind assertion (both positive and negative), the pattern used for the assertion cannot <em>imply</em> matching variable length of text. Fixed length quantifier is allowed. Different length alternations are not allowed, even if the individual alternations are of fixed length.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>s </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'pore42 tar3 dare7 care5'
</span><span>
</span><span style="color:#7f8989;"># not allowed
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?<=</span><span style="color:#7c8f4c;">tar</span><span style="color:#72ab00;">|</span><span style="color:#7c8f4c;">dare)</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#d07711;">'</span><span>, s)
</span><span>re.error: look</span><span style="color:#72ab00;">-</span><span>behind requires fixed</span><span style="color:#72ab00;">-</span><span>width pattern
</span><span>
</span><span style="color:#7f8989;"># workaround for r'(?<!tar|dare)\d+'
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?<!</span><span style="color:#7c8f4c;">tar)(</span><span style="color:#aeb52b;">?<!</span><span style="color:#7c8f4c;">dare)</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#d07711;">'</span><span>, s)
</span><span>[</span><span style="color:#d07711;">'42'</span><span>, </span><span style="color:#d07711;">'5'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># workaround for r'(?<=tar|dare)\d+'
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(?:(</span><span style="color:#aeb52b;">?<=</span><span style="color:#7c8f4c;">tar)</span><span style="color:#72ab00;">|</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?<=</span><span style="color:#7c8f4c;">dare))</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#d07711;">'</span><span>, s)
</span><span>[</span><span style="color:#d07711;">'3'</span><span>, </span><span style="color:#d07711;">'7'</span><span>]
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> The third-party <code>regex</code> module (<a href="https://pypi.org/project/regex/">https://pypi.org/project/regex/</a>) offers advanced features like variable-length lookbehinds, subexpression calls, etc.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/Bu27WS-GExk" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> and <a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> ebooks.</p>
Vim tip 30: some general Vim settings2023-08-08T00:00:00+00:002023-08-08T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-30/<p>Here are some general Vim settings that you can put in the <code>vimrc</code> file to customize your editor. See <a href="https://vimhelp.org/options.txt.html">:h options.txt</a> for complete reference.</p>
<ul>
<li><kbd>set history=200</kbd> increase default history from 50 to 200
<ul>
<li>there are separate history lists for <code>:</code> commands, search patterns, etc</li>
</ul>
</li>
<li><kbd>set nobackup</kbd> disable backup files</li>
<li><kbd>set noswapfile</kbd> disable swap files</li>
<li><kbd>colorscheme murphy</kbd> a dark theme
<ul>
<li>you can use <kbd>:colorscheme</kbd> followed by a space and then press <kbd>Tab</kbd> or <kbd>Ctrl</kbd>+<kbd>d</kbd> to get a list of the available color schemes</li>
</ul>
</li>
<li><kbd>set showcmd</kbd> show partial Normal mode command on Command-line and character/line/block-selection for Visual mode</li>
<li><kbd>set wildmode=longest,list,full</kbd> use Bash-like tab completion
<ul>
<li>first tab will complete as much as possible</li>
<li>second tab will provide a list</li>
<li>third and subsequent tabs will cycle through the completion options</li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> <kbd>:h 'history'</kbd> will give you the documentation for the given option (note the use of single quotes).</p>
<p><img src="/images/info.svg" alt="info" /> You can use these settings from the Command-line mode as well, but will be active for the current Vim session only. Settings specified in the <code>vimrc</code> file will be loaded automatically at startup.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/TnfScldL8fE" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 31: concatenate files column wise2023-08-01T00:00:00+00:002023-08-01T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-31/<p>The <code>paste</code> command is typically used to merge two or more files column wise. By default, <code>paste</code> adds a tab character between corresponding lines of input files.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat colors_1.txt
</span><span style="color:#5597d6;">Blue
</span><span style="color:#5597d6;">Brown
</span><span style="color:#5597d6;">Orange
</span><span style="color:#5597d6;">Purple
</span><span>$ cat colors_2.txt
</span><span style="color:#5597d6;">Black
</span><span style="color:#5597d6;">Blue
</span><span style="color:#5597d6;">Green
</span><span style="color:#5597d6;">Orange
</span><span>
</span><span>$ paste colors_1.txt colors_2.txt
</span><span style="color:#5597d6;">Blue Black
</span><span style="color:#5597d6;">Brown Blue
</span><span style="color:#5597d6;">Orange Green
</span><span style="color:#5597d6;">Purple Orange
</span></code></pre>
<p>You can use the <code>-d</code> option to change the delimiter between the columns. The separator is added even if the data has been exhausted for some of the input files.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ paste </span><span style="color:#72ab00;">-</span><span>d</span><span style="color:#d07711;">'|' </span><span style="color:#72ab00;"><</span><span>(seq </span><span style="color:#b3933a;">3</span><span>) </span><span style="color:#72ab00;"><</span><span>(seq </span><span style="color:#b3933a;">4 5</span><span>) </span><span style="color:#72ab00;"><</span><span>(seq </span><span style="color:#b3933a;">6 8</span><span>)
</span><span style="color:#b3933a;">1</span><span style="color:#72ab00;">|</span><span style="color:#b3933a;">4</span><span style="color:#72ab00;">|</span><span style="color:#b3933a;">6
</span><span style="color:#b3933a;">2</span><span style="color:#72ab00;">|</span><span style="color:#b3933a;">5</span><span style="color:#72ab00;">|</span><span style="color:#b3933a;">7
</span><span style="color:#b3933a;">3</span><span style="color:#72ab00;">||</span><span style="color:#b3933a;">8
</span><span>
</span><span style="color:#7f8989;"># note that the space between -d and empty string is necessary here
</span><span>$ paste </span><span style="color:#72ab00;">-</span><span>d </span><span style="color:#d07711;">'' </span><span style="color:#72ab00;"><</span><span>(seq </span><span style="color:#b3933a;">3</span><span>) </span><span style="color:#72ab00;"><</span><span>(seq </span><span style="color:#b3933a;">6 8</span><span>)
</span><span style="color:#b3933a;">16
</span><span style="color:#b3933a;">27
</span><span style="color:#b3933a;">38
</span><span>
</span><span style="color:#7f8989;"># use newline separator to interleave file contents
</span><span>$ paste </span><span style="color:#72ab00;">-</span><span>d</span><span style="color:#d07711;">'\n' </span><span style="color:#72ab00;"><</span><span>(seq </span><span style="color:#b3933a;">11 12</span><span>) </span><span style="color:#72ab00;"><</span><span>(seq </span><span style="color:#b3933a;">101 102</span><span>)
</span><span style="color:#b3933a;">11
</span><span style="color:#b3933a;">101
</span><span style="color:#b3933a;">12
</span><span style="color:#b3933a;">102
</span></code></pre>
<p>You can use empty files to get multicharacter separation between the columns. The <code>pr</code> command is better suited for this task.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ paste </span><span style="color:#72ab00;">-</span><span>d</span><span style="color:#d07711;">' : ' </span><span style="color:#72ab00;"><</span><span>(seq </span><span style="color:#b3933a;">3</span><span>) </span><span style="color:#72ab00;">/</span><span>dev</span><span style="color:#72ab00;">/</span><span>null </span><span style="color:#72ab00;">/</span><span>dev</span><span style="color:#72ab00;">/</span><span>null </span><span style="color:#72ab00;"><</span><span>(seq </span><span style="color:#b3933a;">4 6</span><span>)
</span><span style="color:#b3933a;">1 </span><span style="color:#72ab00;">: </span><span style="color:#b3933a;">4
</span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;">: </span><span style="color:#b3933a;">5
</span><span style="color:#b3933a;">3 </span><span style="color:#72ab00;">: </span><span style="color:#b3933a;">6
</span><span>
</span><span>$ pr </span><span style="color:#72ab00;">-</span><span>mts</span><span style="color:#d07711;">' : ' </span><span style="color:#72ab00;"><</span><span>(seq </span><span style="color:#b3933a;">3</span><span>) </span><span style="color:#72ab00;"><</span><span>(seq </span><span style="color:#b3933a;">4 6</span><span>)
</span><span style="color:#b3933a;">1 </span><span style="color:#72ab00;">: </span><span style="color:#b3933a;">4
</span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;">: </span><span style="color:#b3933a;">5
</span><span style="color:#b3933a;">3 </span><span style="color:#72ab00;">: </span><span style="color:#b3933a;">6
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/vnGeEO3d42U" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://learnbyexample.github.io/cli_text_processing_coreutils/paste.html">paste command</a> chapter from my <a href="https://github.com/learnbyexample/cli_text_processing_coreutils">Command line text processing with GNU Coreutils</a> ebook for more details.</p>
Python tip 31: next() function2023-07-25T00:00:00+00:002023-07-25T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-31/<p>The <a href="https://docs.python.org/3/library/functions.html#next"><code>next()</code></a> builtin function can be used on an iterator (but not iterables) to retrieve the next item. Once you have exhausted an iterator, trying to get another item will result in a <code>StopIteration</code> exception. Here's an example:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>names </span><span style="color:#72ab00;">= </span><span>(m </span><span style="color:#72ab00;">for </span><span>m </span><span style="color:#72ab00;">in </span><span style="color:#b39f04;">dir</span><span>(</span><span style="color:#a2a001;">tuple</span><span>) </span><span style="color:#72ab00;">if </span><span style="color:#d07711;">'__' </span><span style="color:#72ab00;">not in </span><span>m)
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">next</span><span>(names)
</span><span style="color:#d07711;">'count'
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">next</span><span>(names)
</span><span style="color:#d07711;">'index'
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">next</span><span>(names)
</span><span style="color:#5597d6;">Traceback </span><span>(most recent call last):
</span><span> File </span><span style="color:#d07711;">"<stdin>"</span><span>, line </span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#72ab00;">in <</span><span>module</span><span style="color:#72ab00;">>
</span><span style="color:#a2a001;">StopIteration
</span></code></pre>
<p>Here's a practical example to get a random item from a <code>list</code> without repetition:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> import </span><span>random
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>names </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'Jo'</span><span>, </span><span style="color:#d07711;">'Ravi'</span><span>, </span><span style="color:#d07711;">'Joe'</span><span>, </span><span style="color:#d07711;">'Raj'</span><span>, </span><span style="color:#d07711;">'Jon'</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>random.</span><span style="color:#5597d6;">shuffle</span><span>(names)
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>random_name </span><span style="color:#72ab00;">= </span><span style="color:#b39f04;">iter</span><span>(names)
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">next</span><span>(random_name)
</span><span style="color:#d07711;">'Jon'
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">next</span><span>(random_name)
</span><span style="color:#d07711;">'Ravi'
</span></code></pre>
<p>You can set a default value to be returned instead of the <code>StopIteration</code> exception. Here's an example:</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#72ab00;">>>></span><span> letters </span><span style="color:#72ab00;">= </span><span style="color:#5597d6;">iter</span><span>(</span><span style="color:#d07711;">'fig'</span><span>)
</span><span>
</span><span style="color:#72ab00;">>>></span><span> next(</span><span style="color:#5597d6;">letters, </span><span style="color:#d07711;">'a'</span><span>)
</span><span style="color:#d07711;">'f'
</span><span style="color:#72ab00;">>>></span><span> next(</span><span style="color:#5597d6;">letters, </span><span style="color:#d07711;">'a'</span><span>)
</span><span style="color:#d07711;">'i'
</span><span style="color:#72ab00;">>>></span><span> next(</span><span style="color:#5597d6;">letters, </span><span style="color:#d07711;">'a'</span><span>)
</span><span style="color:#d07711;">'g'
</span><span style="color:#72ab00;">>>></span><span> next(</span><span style="color:#5597d6;">letters, </span><span style="color:#d07711;">'a'</span><span>)
</span><span style="color:#d07711;">'a'
</span><span style="color:#72ab00;">>>></span><span> next(</span><span style="color:#5597d6;">letters, </span><span style="color:#d07711;">'a'</span><span>)
</span><span style="color:#d07711;">'a'
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/CxgjN1V5vA0" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 29: greedy quantifiers2023-07-19T00:00:00+00:002023-07-19T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-29/<p>Quantifiers can be applied to literal characters, dot metacharacter, groups, backreferences and character classes.</p>
<ul>
<li><code>*</code> match zero or more times
<ul>
<li><code>abc*</code> matches <code>ab</code> or <code>abc</code> or <code>abccc</code> or <code>abcccccc</code> but not <code>bc</code></li>
<li><code>Error.*valid</code> matches <code>Error: invalid input</code> but not <code>valid Error</code></li>
<li><code>s/a.*b/X/</code> replaces <code>table bottle bus</code> with <code>tXus</code> since <code>a.*b</code> matches from the first <code>a</code> to the last <code>b</code></li>
</ul>
</li>
<li><code>\+</code> match one or more times
<ul>
<li><code>abc\+</code> matches <code>abc</code> or <code>abccc</code> but not <code>ab</code> or <code>bc</code></li>
</ul>
</li>
<li><code>\?</code> match zero or one times
<ul>
<li><code>\=</code> can also be used, helpful if you are searching backwards with the <code>?</code> command</li>
<li><code>abc\?</code> matches <code>ab</code> or <code>abc</code>. This will match <code>abccc</code> or <code>abcccccc</code> as well, but only the <code>abc</code> portion</li>
<li><code>s/abc\?/X/</code> replaces <code>abcc</code> with <code>Xc</code></li>
</ul>
</li>
<li><code>\{m,n}</code> match <code>m</code> to <code>n</code> times (inclusive)
<ul>
<li><code>ab\{1,4}c</code> matches <code>abc</code> or <code>abbc</code> or <code>xabbbcz</code> but not <code>ac</code> or <code>abbbbbc</code></li>
</ul>
</li>
<li><code>\{m,}</code> match at least <code>m</code> times
<ul>
<li><code>ab\{3,}c</code> matches <code>xabbbcz</code> or <code>abbbbbc</code> but not <code>ac</code> or <code>abc</code> or <code>abbc</code></li>
</ul>
</li>
<li><code>\{,n}</code> match up to <code>n</code> times (including <code>0</code> times)
<ul>
<li><code>ab\{,2}c</code> matches <code>abc</code> or <code>ac</code> or <code>abbc</code> but not <code>xabbbcz</code> or <code>abbbbbc</code></li>
</ul>
</li>
<li><code>\{n}</code> match exactly <code>n</code> times
<ul>
<li><code>ab\{3}c</code> matches <code>xabbbcz</code> but not <code>abbc</code> or <code>abbbbbc</code></li>
</ul>
</li>
</ul>
<p>Greedy quantifiers will consume as <em>much</em> as possible, provided the overall pattern is also matched. That's how the <code>Error.*valid</code> example worked. If <code>.*</code> had consumed everything after <code>Error</code>, there wouldn't be any more characters to try to match <code>valid</code>. How the regexp engine handles matching varying amount of characters depends on the implementation details (backtracking, NFA, etc).</p>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/pattern.txt.html#pattern-overview">:h pattern-overview</a> for more details.</p>
<p><img src="/images/info.svg" alt="info" /> If you are familiar with other regular expression flavors like Perl, Python, etc, you'd be surprised by the use of <code>\</code> in the above examples. If you use <code>\v</code> very magic modifier, the <code>\</code> won't be needed.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/pg1OX4pHN8M" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 30: extract only the matching portions2023-07-11T00:00:00+00:002023-07-11T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-30/<p>The <code>grep</code> command provides the <code>-o</code> option to extract only the matching portions. Here are some examples using the BRE/ERE regexp flavors:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># whole words made up of lowercase alphabets and digits only
</span><span>$ s=</span><span style="color:#d07711;">'coat Bin food Apple (tar12) best fig_42'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>owE </span><span style="color:#d07711;">'[a-z0-9]+'
</span><span>coat
</span><span>food
</span><span>tar12
</span><span>best
</span><span>
</span><span style="color:#7f8989;"># extract characters from the start of string based on a delimiter
</span><span>$ echo </span><span style="color:#d07711;">'apple:123:banana:cherry' </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>o </span><span style="color:#d07711;">'^[^:]*'
</span><span>apple
</span><span>
</span><span style="color:#7f8989;"># sequence of characters surrounded by double quotes
</span><span>$ echo </span><span style="color:#d07711;">'I like "mango" and "guava"' </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>oE </span><span style="color:#d07711;">'"[^"]+"'
</span><span style="color:#d07711;">"mango"
</span><span style="color:#d07711;">"guava"
</span><span>
</span><span style="color:#7f8989;"># whole words that have at least one consecutive repeated character
</span><span>$ s=</span><span style="color:#d07711;">'effort flee facade oddball rat tool'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>owE </span><span style="color:#d07711;">'\w*(\w)\1\w*'
</span><span>effort
</span><span>flee
</span><span>oddball
</span><span>tool
</span></code></pre>
<p>And here are some examples with the PCRE flavor:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># numbers >= 100 if there are leading zeros
</span><span style="color:#7f8989;"># same as: grep -owE '0*[1-9][0-9]{2,}'
</span><span>$ echo </span><span style="color:#d07711;">'0501 035 154 12 26 98234' </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>woP </span><span style="color:#d07711;">'0*+\d{3,}'
</span><span style="color:#b3933a;">0501
</span><span style="color:#b3933a;">154
</span><span style="color:#b3933a;">98234
</span><span>
</span><span style="color:#7f8989;"># extract digits only if it is preceded by - and not followed by ,
</span><span>$ s=</span><span style="color:#d07711;">'42 apple-5, fig3; x-83, y-20: f12'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>oP </span><span style="color:#d07711;">'(?<=-)\d++(?!,)'
</span><span style="color:#b3933a;">20
</span><span>
</span><span style="color:#7f8989;"># extract digits that follow =
</span><span>$ echo </span><span style="color:#d07711;">'apple=42, fig=314' </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>oP </span><span style="color:#d07711;">'=\K\d+'
</span><span style="color:#b3933a;">42
</span><span style="color:#b3933a;">314
</span><span>
</span><span style="color:#7f8989;"># all digits and optional hyphen combo from the start of string
</span><span>$ echo </span><span style="color:#d07711;">'123-87-593 42 apple-12-345' </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>oP </span><span style="color:#d07711;">'\G\d+-?'
</span><span style="color:#b3933a;">123</span><span style="color:#72ab00;">-
</span><span style="color:#b3933a;">87</span><span style="color:#72ab00;">-
</span><span style="color:#b3933a;">593
</span><span>
</span><span style="color:#7f8989;"># all words except those surrounded by double quotes
</span><span>$ s=</span><span style="color:#d07711;">'I like2 "mango" and "guava"'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>oP </span><span style="color:#d07711;">'"[^"]+"(*SKIP)(*F)|\w+'
</span><span style="color:#5597d6;">I
</span><span>like2
</span><span style="color:#72ab00;">and
</span></code></pre>
<p>Use <code>ripgrep</code> if you want to add some more text to the matching portions, or perhaps you need to handle multiple capture groups. Here's an example:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ echo </span><span style="color:#d07711;">'apple=42, fig=314' </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">-</span><span>o </span><span style="color:#d07711;">'(\w+)=(\d+)' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'$2:$1'
</span><span style="color:#b3933a;">42:apple
</span><span style="color:#b3933a;">314:fig
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/MDQCnXRNLoY" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep">CLI text processing with GNU grep and ripgrep</a> ebook if you are interested in learning about the <code>GNU grep</code> and <code>ripgrep</code> commands in more detail.</p>
Python tip 30: zip() function2023-07-04T00:00:00+00:002023-07-04T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-30/<p>You can use the <a href="https://docs.python.org/3/library/functions.html#zip">zip()</a> builtin function to iterate over two or more iterables simultaneously. In every iteration, you'll get a <code>tuple</code> with an item from each of the iterables. Here's an example:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>names </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'Joe'</span><span>, </span><span style="color:#d07711;">'Mei'</span><span>, </span><span style="color:#d07711;">'Rose'</span><span>, </span><span style="color:#d07711;">'Ram'</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>physics </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#b3933a;">86</span><span>, </span><span style="color:#b3933a;">91</span><span>, </span><span style="color:#b3933a;">76</span><span>, </span><span style="color:#b3933a;">80</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>maths </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#b3933a;">77</span><span>, </span><span style="color:#b3933a;">92</span><span>, </span><span style="color:#b3933a;">81</span><span>, </span><span style="color:#b3933a;">83</span><span>]
</span><span>
</span><span style="color:#72ab00;">>>> for </span><span>n, p, m </span><span style="color:#72ab00;">in </span><span style="color:#b39f04;">zip</span><span>(names, physics, maths):
</span><span style="color:#b3933a;">... </span><span style="color:#b39f04;">print</span><span>(</span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{n</span><span style="color:#b3933a;">:5</span><span>}</span><span style="color:#d07711;">: </span><span>{p}</span><span style="color:#d07711;">,</span><span>{m}</span><span style="color:#d07711;">'</span><span>)
</span><span style="color:#b3933a;">...
</span><span>Joe : </span><span style="color:#b3933a;">86</span><span>,</span><span style="color:#b3933a;">77
</span><span>Mei : </span><span style="color:#b3933a;">91</span><span>,</span><span style="color:#b3933a;">92
</span><span>Rose : </span><span style="color:#b3933a;">76</span><span>,</span><span style="color:#b3933a;">81
</span><span>Ram : </span><span style="color:#b3933a;">80</span><span>,</span><span style="color:#b3933a;">83
</span></code></pre>
<p>Here are some examples using list comprehensions and generator expressions:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>p </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">5</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>q </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">214</span><span>, </span><span style="color:#b3933a;">53</span><span>]
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>[i </span><span style="color:#72ab00;">+ </span><span>j </span><span style="color:#72ab00;">for </span><span>i, j </span><span style="color:#72ab00;">in </span><span style="color:#b39f04;">zip</span><span>(p, q)]
</span><span>[</span><span style="color:#b3933a;">4</span><span>, </span><span style="color:#b3933a;">217</span><span>, </span><span style="color:#b3933a;">58</span><span>]
</span><span>
</span><span style="color:#7f8989;"># inner product
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">sum</span><span>(i </span><span style="color:#72ab00;">* </span><span>j </span><span style="color:#72ab00;">for </span><span>i, j </span><span style="color:#72ab00;">in </span><span style="color:#b39f04;">zip</span><span>(p, q))
</span><span style="color:#b3933a;">910
</span></code></pre>
<p>By default, <code>zip()</code> will silently stop when the shortest iterable is exhausted:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>fruits </span><span style="color:#72ab00;">= </span><span>(</span><span style="color:#d07711;">'apple'</span><span>, </span><span style="color:#d07711;">'banana'</span><span>, </span><span style="color:#d07711;">'fig'</span><span>, </span><span style="color:#d07711;">'guava'</span><span>)
</span><span style="color:#72ab00;">>>> </span><span>qty </span><span style="color:#72ab00;">= </span><span>(</span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#b3933a;">25</span><span>, </span><span style="color:#b3933a;">42</span><span>)
</span><span>
</span><span style="color:#72ab00;">>>> for </span><span>f, q </span><span style="color:#72ab00;">in </span><span style="color:#b39f04;">zip</span><span>(fruits, qty):
</span><span style="color:#b3933a;">... </span><span style="color:#b39f04;">print</span><span>(</span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{f</span><span style="color:#b3933a;">:6</span><span>}</span><span style="color:#d07711;">: </span><span>{q}</span><span style="color:#d07711;">'</span><span>)
</span><span style="color:#b3933a;">...
</span><span>apple : </span><span style="color:#b3933a;">100
</span><span>banana: </span><span style="color:#b3933a;">25
</span><span>fig : </span><span style="color:#b3933a;">42
</span></code></pre>
<p>The <code>strict</code> keyword argument was added in the Python 3.10 version. When set to <code>True</code>, this will raise an exception if the iterables are not of the same length:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> for </span><span>f, q </span><span style="color:#72ab00;">in </span><span style="color:#b39f04;">zip</span><span>(fruits, qty, </span><span style="color:#5597d6;">strict</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">True</span><span>):
</span><span style="color:#b3933a;">... </span><span style="color:#b39f04;">print</span><span>(</span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{f</span><span style="color:#b3933a;">:6</span><span>}</span><span style="color:#d07711;">: </span><span>{q}</span><span style="color:#d07711;">'</span><span>)
</span><span style="color:#b3933a;">...
</span><span>apple : </span><span style="color:#b3933a;">100
</span><span>banana: </span><span style="color:#b3933a;">25
</span><span>fig : </span><span style="color:#b3933a;">42
</span><span style="color:#5597d6;">Traceback </span><span>(most recent call last):
</span><span> File </span><span style="color:#d07711;">"<stdin>"</span><span>, line </span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#72ab00;">in <</span><span>module</span><span style="color:#72ab00;">>
</span><span style="color:#a2a001;">ValueError</span><span>: </span><span style="color:#b39f04;">zip</span><span>() argument </span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;">is </span><span>shorter than argument </span><span style="color:#b3933a;">1
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://docs.python.org/3/library/itertools.html#itertools.zip_longest">itertools.zip_longest()</a> and <a href="https://stackoverflow.com/q/61126284/4082052">stackoverflow: zipped Python generators with 2nd one being shorter</a>.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/4vR9x30enWU" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
CLI text processing with GNU sed book announcement2023-06-29T00:00:00+00:002023-06-29T00:00:00+00:00https://learnbyexample.github.io/cli-text-processing-sed-announcement/<p>Hello!</p>
<p>I am pleased to announce a new version of my <strong>CLI text processing with GNU sed</strong> ebook. Examples, exercises, solutions, descriptions and external links were added/updated/corrected.</p>
<p>This book will help you learn the <code>GNU sed</code> command step-by-step from beginner to advanced levels with <strong>hundreds of examples and exercises</strong>. In addition to command options, <strong>regular expressions</strong> will also be discussed in detail.</p>
<span id="continue-reading"></span><br>
<h2 id="release-offers">Release offers<a class="zola-anchor" href="#release-offers" aria-label="Anchor link for: release-offers">🔗</a></h2>
<p>To celebrate the new release, you can download PDF/EPUB versions of <strong>CLI text processing with GNU sed</strong> for FREE till 10-July-2023. You can still pay if you wish ;)</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/gnu_sed">Gumroad</a></li>
<li><a href="https://leanpub.com/gnu_sed/c/new_sed_release">Leanpub</a></li>
</ul>
<p>Other offers:</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/gnugrep_ripgrep">CLI text processing with GNU grep and ripgrep</a> is FREE</li>
<li><a href="https://learnbyexample.gumroad.com/l/all-books/new_sed_release">All Books Bundle</a> is $12 (normal price $32) — all my 13 programming ebooks</li>
</ul>
<br>
<h2 id="what-s-new">What's new?<a class="zola-anchor" href="#what-s-new" aria-label="Anchor link for: what-s-new">🔗</a></h2>
<ul>
<li>Command version updated to <strong>GNU sed 4.9</strong></li>
<li>Many more exercises added, and you can practice some of them using this <a href="https://github.com/learnbyexample/TUI-apps/blob/main/SedExercises">interactive TUI app</a></li>
<li>Long sections split into smaller ones</li>
<li>In general, many of the examples, exercises, solutions, descriptions and external links were updated/corrected</li>
<li>Updated Acknowledgements section</li>
<li>Code snippets related to info/warning sections will now appear as a single block</li>
<li>Book title changed to <strong>CLI text processing with GNU sed</strong></li>
<li>New cover image</li>
<li>Images centered for EPUB format</li>
</ul>
<br>
<h2 id="videos">Videos<a class="zola-anchor" href="#videos" aria-label="Anchor link for: videos">🔗</a></h2>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/nvKyKoeiZD8" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<p>On this blog, I <a href="https://learnbyexample.github.io/tips/">post tips</a> covering Python, command line tools and Vim. Here are video demos for these tips:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=THSMmCZQn1A&list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F">Python tips</a></li>
<li><a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY">Linux command line tips</a></li>
</ul>
<br>
<h2 id="interactive-tui-app">Interactive TUI app<a class="zola-anchor" href="#interactive-tui-app" aria-label="Anchor link for: interactive-tui-app">🔗</a></h2>
<p>I also wrote an <a href="https://github.com/learnbyexample/TUI-apps/blob/main/SedExercises">interactive TUI app</a> based on some of the exercises from the ebook. Reference solutions are also provided.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/SedExercises/sed_exercises.png" alt="Sample screenshot from the interactive TUI app for sed exercises" loading="lazy" /></p>
<br>
<h2 id="table-of-contents">Table of Contents<a class="zola-anchor" href="#table-of-contents" aria-label="Anchor link for: table-of-contents">🔗</a></h2>
<ol>
<li>Preface</li>
<li>Introduction</li>
<li>In-place file editing</li>
<li>Selective editing</li>
<li>BRE/ERE Regular Expressions</li>
<li>Flags</li>
<li>Shell substitutions</li>
<li>z, s and f command line options</li>
<li>append, change, insert</li>
<li>Adding content from file</li>
<li>Control structures</li>
<li>Processing lines bounded by distinct markers</li>
<li>Gotchas and Tricks</li>
<li>Further Reading</li>
</ol>
<br>
<h2 id="web-version">Web version<a class="zola-anchor" href="#web-version" aria-label="Anchor link for: web-version">🔗</a></h2>
<p>You can also read the book online here: <a href="https://learnbyexample.github.io/learn_gnused/">https://learnbyexample.github.io/learn_gnused/</a>.</p>
<br>
<h2 id="github-repo">GitHub repo<a class="zola-anchor" href="#github-repo" aria-label="Anchor link for: github-repo">🔗</a></h2>
<p>Visit <a href="https://github.com/learnbyexample/learn_gnused">https://github.com/learnbyexample/learn_gnused</a> for markdown source, example files, exercise solutions, sample chapters and other details related to the book.</p>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://learnbyexample.github.io/customizing-pandoc/">my blog post</a> on how to customize <code>pandoc</code> for generating beautiful PDF/EPUB versions from GitHub style markdown.</p>
</blockquote>
<br>
<h2 id="newsletter">Newsletter<a class="zola-anchor" href="#newsletter" aria-label="Anchor link for: newsletter">🔗</a></h2>
<p>Subscribe to <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> — free newsletter covering programming resources, updates on what I am creating, tips, tools, free ebooks and more, delivered every Friday.</p>
<br>
<h2 id="feedback-and-errata">Feedback and Errata<a class="zola-anchor" href="#feedback-and-errata" aria-label="Anchor link for: feedback-and-errata">🔗</a></h2>
<p>I would highly appreciate it if you'd <strong>let me know how you felt about this book</strong>. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.</p>
<p>You can reach me via:</p>
<ul>
<li>Issue Manager: <a href="https://github.com/learnbyexample/learn_gnused/issues">https://github.com/learnbyexample/learn_gnused/issues</a></li>
<li>E-mail: <code>echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode</code></li>
<li>Twitter: <a href="https://twitter.com/learn_byexample">https://twitter.com/learn_byexample</a></li>
</ul>
<p>Happy learning :)</p>
Vim tip 28: miscellaneous motion and reposition commands2023-06-26T00:00:00+00:002023-06-26T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-28/<p>Moving within the visible window:</p>
<ul>
<li><kbd>H</kbd> move to the first non-blank character of the top (home) line of the visible window</li>
<li><kbd>M</kbd> move to the first non-blank character of the middle line of the visible window</li>
<li><kbd>L</kbd> move to the first non-blank character of the bottom (low) line of the visible window</li>
</ul>
<p>Reposition the current line:</p>
<ul>
<li><kbd>Ctrl</kbd>+<kbd>e</kbd> scroll up by a line</li>
<li><kbd>Ctrl</kbd>+<kbd>y</kbd> scroll down by a line</li>
<li><kbd>zz</kbd> reposition the current line to the middle of the visible window
<ul>
<li>useful to see context around lines that are nearer to the top/bottom of the visible window</li>
</ul>
</li>
<li><kbd>zt</kbd> reposition the current line to the top of the visible window</li>
<li><kbd>zb</kbd> reposition the current line to the bottom of the visible window</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/options.txt.html#%27scrolloff%27">:h 'scrolloff'</a> option if you want to always show context around the current line.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/XRXO4Ns8rPE" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 29: define fields using FPAT in GNU awk2023-06-20T00:00:00+00:002023-06-20T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-29/<p>In <code>awk</code>, the <code>FS</code> variable allows you to define the input field <em>separator</em>. In contrast, <code>FPAT</code> (field pattern) allows you to define what should the fields be made up of.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'Sample123string42with777numbers'
</span><span style="color:#7f8989;"># one or more consecutive digits
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">FPAT</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'[0-9]+' '{print $2}'
</span><span style="color:#b3933a;">42
</span><span>
</span><span>$ s=</span><span style="color:#d07711;">'coat Bin food tar12 best Apple fig_42'
</span><span style="color:#7f8989;"># whole words made up of lowercase alphabets and digits only
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">FPAT</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\\</span><span style="color:#d07711;"><[a-z0-9]+</span><span style="color:#aeb52b;">\\</span><span style="color:#d07711;">>' </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">OFS</span><span style="color:#72ab00;">=</span><span>, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span>coat,food,tar12,best
</span><span>
</span><span>$ s=</span><span style="color:#d07711;">'items: "apple" and "mango"'
</span><span style="color:#7f8989;"># get the first double quoted item
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">FPAT</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'"[^"]+"' '{print $1}'
</span><span style="color:#d07711;">"apple"
</span></code></pre>
<p><code>FPAT</code> is often used for CSV input where fields can contain embedded delimiter characters. For example, a field content <code>"fox,42"</code> when <code>,</code> is the delimiter.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'eagle,"fox,42",bee,frog'
</span><span>
</span><span style="color:#7f8989;"># simply using , as separator isn't sufficient
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">F</span><span>, </span><span style="color:#d07711;">'{print $2}'
</span><span style="color:#d07711;">"fox
</span></code></pre>
<p>For such simpler CSV input, <code>FPAT</code> helps to define fields as starting and ending with double quotes or containing non-comma characters.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># * is used instead of + to allow empty fields
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">FPAT</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'"[^"]*"|[^,]*' '{print $2}'
</span><span style="color:#d07711;">"fox,42"
</span></code></pre>
<p><img src="/images/warning.svg" alt="warning" /> The above will not work for all kinds of CSV files, for example if fields contain escaped double quotes, newline characters, etc. See <a href="https://stackoverflow.com/q/45420535/4082052">stackoverflow: What's the most robust way to efficiently parse CSV using awk?</a> for such cases. You could also use other programming languages such as Perl, Python, Ruby, etc which come with standard CSV parsing libraries or have easy access to third party solutions. There are also specialized command line tools such as <a href="https://github.com/BurntSushi/xsv">xsv</a>.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/1ZQni88a99w" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/learn_gnuawk">CLI text processing with GNU awk</a> ebook.</p>
Python tip 29: negative lookarounds2023-06-13T00:00:00+00:002023-06-13T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-29/<p>Lookarounds help to create custom anchors and add conditions within a regex definition. These assertions are also known as <strong>zero-width patterns</strong> because they add restrictions similar to anchors and are not part of the matched portions. The syntax for negative lookarounds is shown below:</p>
<ul>
<li><code>(?!pat)</code> negative lookahead assertion</li>
<li><code>(?<!pat)</code> negative lookbehind assertion</li>
</ul>
<p>Here are some examples:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># change 'cat' only if it is not followed by a digit character
</span><span style="color:#7f8989;"># note that the end of string satisfies the given assertion
</span><span style="color:#7f8989;"># 'catcat' has two matches as the assertion doesn't consume characters
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">cat(</span><span style="color:#aeb52b;">?!\d</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'dog'</span><span>, </span><span style="color:#d07711;">'hey cats! cat42 cat_5 catcat'</span><span>)
</span><span style="color:#d07711;">'hey dogs! cat42 dog_5 dogdog'
</span><span>
</span><span style="color:#7f8989;"># change 'cat' only if it is not preceded by _
</span><span style="color:#7f8989;"># note how 'cat' at the start of string is matched as well
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?<!</span><span style="color:#7c8f4c;">_)cat</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'dog'</span><span>, </span><span style="color:#d07711;">'cat _cat 42catcat'</span><span>)
</span><span style="color:#d07711;">'dog _cat 42dogdog'
</span><span>
</span><span style="color:#7f8989;"># change whole word only if it is not preceded by : or -
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?<![:-]</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">\b</span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">+</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'X'</span><span>, </span><span style="color:#d07711;">':cart <apple: -rest ;tea'</span><span>)
</span><span style="color:#d07711;">':cart <X: -rest ;X'
</span></code></pre>
<p>Lookarounds can be placed anywhere and multiple lookarounds can be combined in any order. They do not consume characters nor do they play a role in matched portions. They just let you know whether the condition you want to test is satisfied from the current location in the input string.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># extract all whole words that do not start with a/n
</span><span style="color:#72ab00;">>>> </span><span>ip </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'a_t row on Urn e note Dust n end a2-e|u'
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?![an]</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">\b</span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">+</span><span style="color:#d07711;">'</span><span>, ip)
</span><span>[</span><span style="color:#d07711;">'row'</span><span>, </span><span style="color:#d07711;">'on'</span><span>, </span><span style="color:#d07711;">'Urn'</span><span>, </span><span style="color:#d07711;">'e'</span><span>, </span><span style="color:#d07711;">'Dust'</span><span>, </span><span style="color:#d07711;">'end'</span><span>, </span><span style="color:#d07711;">'e'</span><span>, </span><span style="color:#d07711;">'u'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># since the three assertions used here are all zero-width,
</span><span style="color:#7f8989;"># all of the 6 possible combinations will be equivalent
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?!</span><span style="color:#72ab00;">\Z</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">\b</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?<!</span><span style="color:#72ab00;">\A</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">' '</span><span>, </span><span style="color:#d07711;">'output=num1+35*42/num2'</span><span>)
</span><span style="color:#d07711;">'output = num1 + 35 * 42 / num2'
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/CoEFqWYRr4Q" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> and <a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> ebooks.</p>
Vim tip 27: regexp anchors2023-06-05T00:00:00+00:002023-06-05T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-27/<p>By default, regexp matches anywhere in the text. You can use line and word anchors to specify additional restrictions regarding the position of matches. These restrictions are made possible by assigning special meaning to certain characters (<strong>metacharacters</strong>) and escape sequences.</p>
<ul>
<li><code>^</code> restricts the match to the start-of-line
<ul>
<li><code>^This</code> matches <code>This is a sample</code> but not <code>Do This</code></li>
</ul>
</li>
<li><code>$</code> restricts the match to the end-of-line
<ul>
<li><code>)$</code> matches <code>apple (5)</code> but not <code>def greeting():</code></li>
</ul>
</li>
<li><code>^$</code> match empty line</li>
<li><code>\<pattern</code> restricts the match to the start of a word
<ul>
<li>word characters include alphabets, digits and underscore</li>
<li><code>\<his</code> matches <code>his</code> or <code>to-his</code> or <code>history</code> but not <code>this</code> or <code>_hist</code></li>
</ul>
</li>
<li><code>pattern\></code> restricts the match to the end of a word
<ul>
<li><code>his\></code> matches <code>his</code> or <code>to-his</code> or <code>this</code> but not <code>history</code> or <code>_hist</code></li>
</ul>
</li>
<li><code>\<pattern\></code> restricts the match between start of a word and end of a word
<ul>
<li><code>\<his\></code> matches <code>his</code> or <code>to-his</code> but not <code>this</code> or <code>history</code> or <code>_hist</code></li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> End-of-line can be <code>\r</code> (carriage return), <code>\n</code> (newline) or <code>\r\n</code> depending on your system and <code>fileformat</code> setting.</p>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/pattern.txt.html#pattern-atoms">:h pattern-atoms</a> for more details.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/AJNWtpKA2zM" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 28: substitute specific occurrence with GNU sed2023-05-30T00:00:00+00:002023-05-30T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-28/<p>By using the <code>g</code> flag with the <code>s</code> command (substitute), you can search and replace all occurrences of a pattern. Without the <code>g</code> flag, only the first matching portion will be replaced.</p>
<p>Did you know that you can use a number as a flag to replace only that particular occurrence of matching parts?</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># replace only the third occurrence of ':' with '---'
</span><span>$ echo </span><span style="color:#d07711;">'apple:banana:cherry:fig:mango' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/:/---/3'
</span><span style="color:#b3933a;">apple:banana:</span><span>cherry</span><span style="color:#72ab00;">---</span><span style="color:#b3933a;">fig:</span><span>mango
</span><span>
</span><span style="color:#7f8989;"># replace only the second occurrence of a word starting with 't'
</span><span>$ echo </span><span style="color:#d07711;">'book table bus car banana tap camp' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/\bt\w*/"&"/2'
</span><span>book table bus car banana </span><span style="color:#d07711;">"tap"</span><span> camp
</span></code></pre>
<p>To replace a specific occurrence from the end of the line, you'll have use regular expression tricks:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'apple:banana:cherry:fig:mango'
</span><span>
</span><span style="color:#7f8989;"># replace the last occurrence
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/(.*):/\1---/'
</span><span style="color:#b3933a;">apple:banana:cherry:</span><span>fig</span><span style="color:#72ab00;">---</span><span>mango
</span><span>
</span><span style="color:#7f8989;"># replace the last but one occurrence
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/(.*):(.*:)/\1---\2/'
</span><span style="color:#b3933a;">apple:banana:</span><span>cherry</span><span style="color:#72ab00;">---</span><span style="color:#b3933a;">fig:</span><span>mango
</span><span>
</span><span style="color:#7f8989;"># generic formula, where {N} refers to the last but Nth occurrence
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/(.*):((.*:){2})/\1---\2/'
</span><span style="color:#b3933a;">apple:</span><span>banana</span><span style="color:#72ab00;">---</span><span style="color:#b3933a;">cherry:fig:</span><span>mango
</span></code></pre>
<p>If you combine a number flag with the <code>g</code> flag, all matches from that particular occurrence will be replaced.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># replace except the first occurrence of a word starting with 'b'
</span><span>$ echo </span><span style="color:#d07711;">'book table bus car banana tap camp' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/\bb\w*/"&"/2g'
</span><span>book table </span><span style="color:#d07711;">"bus"</span><span> car </span><span style="color:#d07711;">"banana" </span><span style="color:#b39f04;">tap</span><span> camp
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/7CxaiYQ2gIU" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/learn_gnused">CLI text processing with GNU sed</a> ebook.</p>
Python tip 28: string concatenation and repetition2023-05-24T00:00:00+00:002023-05-24T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-28/<p>Python provides a wide variety of features to work with strings. In this tip, you'll learn about string concatenation and repetition.</p>
<p>The <code>+</code> operator is one of the ways to concatenate two strings. The operands can be any expression that results in a string value and you can use any of the different ways to specify a string literal. Another option is to use f-strings. Here are some examples:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>s1 </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'hello'
</span><span style="color:#72ab00;">>>> </span><span>s2 </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'world'
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">print</span><span>(s1 </span><span style="color:#72ab00;">+ </span><span style="color:#d07711;">' ' </span><span style="color:#72ab00;">+ </span><span>s2)
</span><span>hello world
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{s1} {s2}</span><span style="color:#d07711;">'
</span><span style="color:#d07711;">'hello world'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>s1 </span><span style="color:#72ab00;">+ </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">.</span><span style="color:#7c8f4c;"> 1</span><span style="color:#aeb52b;">\n</span><span style="color:#7c8f4c;">2</span><span style="color:#d07711;">'
</span><span style="color:#d07711;">'hello. 1</span><span style="color:#aeb52b;">\\</span><span style="color:#d07711;">n2'
</span></code></pre>
<p>Another way to concatenate is to simply place any kind of string literal next to each other. You can use zero or more whitespaces between the two literals. But you cannot mix an expression and a string literal. If the strings are inside parentheses, you can also use newline characters to separate the literals and optionally use comments.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">'hello' </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">.</span><span style="color:#7c8f4c;"> 1</span><span style="color:#aeb52b;">\n</span><span style="color:#7c8f4c;">2</span><span style="color:#d07711;">'
</span><span style="color:#d07711;">'hello. 1</span><span style="color:#aeb52b;">\\</span><span style="color:#d07711;">n2'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">print</span><span>(</span><span style="color:#d07711;">'apple'
</span><span style="color:#b3933a;">... </span><span style="color:#d07711;">'-banana'
</span><span style="color:#b3933a;">... </span><span style="color:#d07711;">'-cherry'</span><span>)
</span><span>apple</span><span style="color:#72ab00;">-</span><span>banana</span><span style="color:#72ab00;">-</span><span>cherry
</span></code></pre>
<p>You can repeat a string by using the <code>*</code> operator between a string and an integer. You'll get an empty string if the integer value is less than <code>1</code>.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>style_char </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'-'
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">print</span><span>(style_char </span><span style="color:#72ab00;">* </span><span style="color:#b3933a;">50</span><span>)
</span><span style="color:#72ab00;">--------------------------------------------------
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>word </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'buffalo '
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">print</span><span>(</span><span style="color:#b3933a;">8 </span><span style="color:#72ab00;">* </span><span>word)
</span><span>buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/nuH7C162W5U" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 26: executing shell commands2023-05-16T00:00:00+00:002023-05-16T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-26/<p>You can execute external commands from within Vim. Here are some examples:</p>
<ul>
<li><kbd>:!ls</kbd> execute the given shell command and display output
<ul>
<li>the results are displayed as part of an expanded Command-line area, doesn't change contents of the file</li>
</ul>
</li>
<li><kbd>:.!date</kbd> replace the current line with the output of the given command
<ul>
<li>pressing <kbd>!!</kbd> in Normal mode will also result in <kbd>:.!</kbd></li>
<li><code>!</code> waits for motion similar to <code>d</code> and <code>y</code> commands, <kbd>!G</kbd> will give <kbd>:.,$!</kbd></li>
</ul>
</li>
<li><kbd>:%!sort</kbd> sort all the lines
<ul>
<li>recall that <code>%</code> is a shortcut for the range <code>1,$</code></li>
<li>note that this executes an external command, not the built-in <code>:sort</code> command</li>
</ul>
</li>
<li><kbd>:3,8!sort</kbd> sort only lines <code>3</code> to <code>8</code></li>
<li><kbd>:r!date</kbd> insert output of the given command below the current line</li>
<li><kbd>:r report.log</kbd> insert contents of the given file below the current line
<ul>
<li>Note that <code>!</code> is not used here since there is no shell command</li>
</ul>
</li>
<li><kbd>:.!grep '^Help ' %</kbd> replace the current line with all the lines starting with <code>Help</code> in the current file
<ul>
<li><code>%</code> here refers to current file contents</li>
</ul>
</li>
<li><kbd>:sh</kbd> open a shell session within Vim
<ul>
<li>use <code>exit</code> command to quit the session</li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/various.txt.html#%3A%21cmd">:h :!</a>, <a href="https://vimhelp.org/various.txt.html#%3Ash">:h :sh</a> and <a href="https://vimhelp.org/insert.txt.html#%3Ar">:h :r</a> for more details.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/STSZt2c1rSA" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI text processing with GNU grep and ripgrep book announcement2023-05-11T00:00:00+00:002023-05-11T00:00:00+00:00https://learnbyexample.github.io/cli-text-processing-grep-announcement/<p>Hello!</p>
<p>I am pleased to announce a new version of my <strong>CLI text processing with GNU grep and ripgrep</strong> ebook. Examples, exercises, solutions, descriptions and external links were added/updated/corrected. The chapter on <code>ripgrep</code> was changed significantly to focus mostly on the differences compared to <code>GNU grep</code>.</p>
<p>This book will help you learn these commands step-by-step from beginner to advanced levels with <strong>hundreds of examples and exercises</strong>.</p>
<span id="continue-reading"></span><br>
<h2 id="release-offers">Release offers<a class="zola-anchor" href="#release-offers" aria-label="Anchor link for: release-offers">🔗</a></h2>
<p>To celebrate the new release, you can download PDF/EPUB versions of <strong>CLI text processing with GNU grep and ripgrep</strong> for FREE till 21-May-2023. You can still pay if you wish ;)</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/gnugrep_ripgrep">Gumroad</a></li>
<li><a href="https://leanpub.com/gnugrep_ripgrep/c/new_grep_release">Leanpub</a></li>
</ul>
<p>Other offers:</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/cli_computing">Computing from the Command Line</a> is FREE — Linux command line tools and Shell Scripting for beginner to intermediate level users</li>
<li><a href="https://learnbyexample.gumroad.com/l/all-books/new_grep_release">All Books Bundle</a> is $12 (normal price $32) — all my 13 programming ebooks</li>
</ul>
<br>
<h2 id="what-s-new">What's new?<a class="zola-anchor" href="#what-s-new" aria-label="Anchor link for: what-s-new">🔗</a></h2>
<ul>
<li>Command versions updated to <strong>GNU grep 3.10</strong> and <strong>ripgrep 13.0.0</strong></li>
<li>Many more exercises added</li>
<li>PCRE chapter — added section for conditional grouping, corrected description and examples for <code>\K</code>, atomic grouping, etc</li>
<li>ripgrep chapter — options and regex section modified to present only differences compared to <code>GNU grep</code>, added details for more options such as <code>--field-match-separator</code>, improved recursive search section, etc</li>
<li>Long sections split into smaller ones</li>
<li>In general, many of the examples, exercises, solutions, descriptions and external links were updated/corrected</li>
<li>Updated Acknowledgements section</li>
<li>Code snippets related to info/warning sections will now appear as a single block</li>
<li>Book title changed to <strong>CLI text processing with GNU grep and ripgrep</strong></li>
<li>New cover image</li>
<li>Images centered for EPUB format</li>
</ul>
<br>
<h2 id="videos">Videos<a class="zola-anchor" href="#videos" aria-label="Anchor link for: videos">🔗</a></h2>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/MSbGokwHm-A" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<p>On this blog, I <a href="https://learnbyexample.github.io/tips/">post tips</a> covering Python, command line tools and Vim. Here are video demos for these tips:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=THSMmCZQn1A&list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F">Python tips</a></li>
<li><a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY">Linux command line tips</a></li>
</ul>
<br>
<h2 id="interactive-tui-app">Interactive TUI app<a class="zola-anchor" href="#interactive-tui-app" aria-label="Anchor link for: interactive-tui-app">🔗</a></h2>
<p>I also wrote an <a href="https://github.com/learnbyexample/TUI-apps/blob/main/GrepExercises">interactive TUI app</a> based on some of the exercises from the ebook. Reference solutions are provided for both <code>GNU grep</code> and <code>ripgrep</code>.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/GrepExercises/grep_exercises.png" alt="Sample screenshot from the interactive TUI app for grep exercises" loading="lazy" /></p>
<br>
<h2 id="table-of-contents">Table of Contents<a class="zola-anchor" href="#table-of-contents" aria-label="Anchor link for: table-of-contents">🔗</a></h2>
<ol>
<li>Preface</li>
<li>Introduction</li>
<li>Frequently used options</li>
<li>BRE/ERE Regular Expressions</li>
<li>Context matching</li>
<li>Recursive search</li>
<li>Miscellaneous options</li>
<li>Perl Compatible Regular Expressions</li>
<li>Gotchas and Tricks</li>
<li>ripgrep</li>
<li>Further Reading</li>
</ol>
<br>
<h2 id="web-version">Web version<a class="zola-anchor" href="#web-version" aria-label="Anchor link for: web-version">🔗</a></h2>
<p>You can also read the book online here: <a href="https://learnbyexample.github.io/learn_gnugrep_ripgrep/">https://learnbyexample.github.io/learn_gnugrep_ripgrep/</a>.</p>
<br>
<h2 id="github-repo">GitHub repo<a class="zola-anchor" href="#github-repo" aria-label="Anchor link for: github-repo">🔗</a></h2>
<p>Visit <a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep">https://github.com/learnbyexample/learn_gnugrep_ripgrep</a> for markdown source, example files, exercise solutions, sample chapters and other details related to the book.</p>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://learnbyexample.github.io/customizing-pandoc/">my blog post</a> on how to customize <code>pandoc</code> for generating beautiful PDF/EPUB versions from GitHub style markdown.</p>
</blockquote>
<br>
<h2 id="newsletter">Newsletter<a class="zola-anchor" href="#newsletter" aria-label="Anchor link for: newsletter">🔗</a></h2>
<p>Subscribe to <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> — free newsletter covering programming resources, updates on what I am creating, tips, tools, free ebooks and more, delivered every Friday.</p>
<br>
<h2 id="feedback-and-errata">Feedback and Errata<a class="zola-anchor" href="#feedback-and-errata" aria-label="Anchor link for: feedback-and-errata">🔗</a></h2>
<p>I would highly appreciate it if you'd <strong>let me know how you felt about this book</strong>. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.</p>
<p>You can reach me via:</p>
<ul>
<li>Issue Manager: <a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep/issues">https://github.com/learnbyexample/learn_gnugrep_ripgrep/issues</a></li>
<li>E-mail: <code>echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode</code></li>
<li>Twitter: <a href="https://twitter.com/learn_byexample">https://twitter.com/learn_byexample</a></li>
</ul>
<p>Happy learning :)</p>
CLI tip 27: reverse text line wise with tac2023-05-09T00:00:00+00:002023-05-09T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-27/<p>You can use <code>tac</code> to reverse the input line wise. If you pass multiple input files, each file content will be reversed separately.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'apple\nbanana\ncherry\nfig and honey\n' </span><span style="color:#72ab00;">|</span><span> tac
</span><span>fig </span><span style="color:#72ab00;">and</span><span> honey
</span><span>cherry
</span><span>banana
</span><span>apple
</span></code></pre>
<p>You can use the <code>-s</code> option to specify a different string to be used as the <em>line</em> separator (newline is the default separator). When the custom separator occurs before the content of interest, use the <code>-b</code> option to print those separators before the content in the output as well.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat blocks.txt
</span><span style="color:#72ab00;">%=%=
</span><span>apple
</span><span>banana
</span><span style="color:#72ab00;">%=%=
</span><span style="color:#b3933a;">1
</span><span style="color:#b3933a;">2
</span><span style="color:#b3933a;">3
</span><span style="color:#72ab00;">%=%=
</span><span>red
</span><span>green
</span><span>
</span><span>$ tac </span><span style="color:#72ab00;">-</span><span>b </span><span style="color:#72ab00;">-</span><span>s </span><span style="color:#d07711;">'%=%='</span><span> blocks.txt
</span><span style="color:#72ab00;">%=%=
</span><span>red
</span><span>green
</span><span style="color:#72ab00;">%=%=
</span><span style="color:#b3933a;">1
</span><span style="color:#b3933a;">2
</span><span style="color:#b3933a;">3
</span><span style="color:#72ab00;">%=%=
</span><span>apple
</span><span>banana
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://learnbyexample.github.io/tips/cli-tip-8/">CLI tip 8: extract from start of file until matching line</a> for a practical example where reversing input content helps in constructing a solution.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/6p_9zP66wqA" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://learnbyexample.github.io/cli_text_processing_coreutils/cat-tac.html#tac">tac</a> section from my <a href="https://github.com/learnbyexample/cli_text_processing_coreutils">Command line text processing with GNU Coreutils</a> ebook for more details and examples.</p>
Python tip 27: enumerate() function2023-05-03T00:00:00+00:002023-05-03T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-27/<p>When you use a <code>for</code> loop, you get one element per each iteration. If you need the <em>index</em> of the elements as well, use the <a href="https://docs.python.org/3/library/functions.html#enumerate">enumerate()</a> built-in function. You'll get a <code>tuple</code> value per each iteration, containing index (starting with <code>0</code> by default) and the value at that index.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>nums </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#b3933a;">42</span><span>, </span><span style="color:#b3933a;">3.14</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#b3933a;">1000</span><span>]
</span><span style="color:#72ab00;">>>> for </span><span>t </span><span style="color:#72ab00;">in </span><span style="color:#b39f04;">enumerate</span><span>(nums):
</span><span style="color:#b3933a;">... </span><span style="color:#b39f04;">print</span><span>(t)
</span><span style="color:#b3933a;">...
</span><span>(</span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#b3933a;">42</span><span>)
</span><span>(</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">3.14</span><span>)
</span><span>(</span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">2</span><span>)
</span><span>(</span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">1000</span><span>)
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>names </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'Jo'</span><span>, </span><span style="color:#d07711;">'Joe'</span><span>, </span><span style="color:#d07711;">'Jon'</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>[(n1, n2) </span><span style="color:#72ab00;">for </span><span>i, n1 </span><span style="color:#72ab00;">in </span><span style="color:#b39f04;">enumerate</span><span>(names) </span><span style="color:#72ab00;">for </span><span>n2 </span><span style="color:#72ab00;">in </span><span>names[i</span><span style="color:#72ab00;">+</span><span style="color:#b3933a;">1</span><span>:]]
</span><span>[(</span><span style="color:#d07711;">'Jo'</span><span>, </span><span style="color:#d07711;">'Joe'</span><span>), (</span><span style="color:#d07711;">'Jo'</span><span>, </span><span style="color:#d07711;">'Jon'</span><span>), (</span><span style="color:#d07711;">'Joe'</span><span>, </span><span style="color:#d07711;">'Jon'</span><span>)]
</span></code></pre>
<p>By setting the <code>start</code> argument, you can change the initial value of the index.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>items </span><span style="color:#72ab00;">= </span><span>(</span><span style="color:#d07711;">'car'</span><span>, </span><span style="color:#d07711;">'table'</span><span>, </span><span style="color:#d07711;">'book'</span><span>)
</span><span style="color:#72ab00;">>>> for </span><span>idx, val </span><span style="color:#72ab00;">in </span><span style="color:#b39f04;">enumerate</span><span>(items, </span><span style="color:#5597d6;">start</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">1</span><span>):
</span><span style="color:#b3933a;">... </span><span style="color:#b39f04;">print</span><span>(</span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{idx}</span><span style="color:#d07711;">: </span><span>{val}</span><span style="color:#d07711;">'</span><span>)
</span><span style="color:#b3933a;">...
</span><span style="color:#b3933a;">1</span><span>: car
</span><span style="color:#b3933a;">2</span><span>: table
</span><span style="color:#b3933a;">3</span><span>: book
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/8a-Lg4mx1wA" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 25: substitute flags2023-04-25T00:00:00+00:002023-04-25T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-25/<p>Here are some of the flags you can use with the substitute command:</p>
<ul>
<li><code>g</code> replace all occurrences within a matching line
<ul>
<li>by default, only the first matching portion will be replaced</li>
</ul>
</li>
<li><code>c</code> ask for confirmation before each replacement</li>
<li><code>i</code> ignore case for <em>searchpattern</em></li>
<li><code>I</code> don't ignore case for <em>searchpattern</em></li>
</ul>
<p>These flags are applicable for the substitute command but not <code>/</code> or <code>?</code> searches. Flags can also be combined, for example:</p>
<ul>
<li><code>s/cat/Dog/gi</code> replace every occurrence of <code>cat</code> with <code>Dog</code>
<ul>
<li>Case is ignored, so <code>Cat</code>, <code>cAt</code>, <code>CAT</code>, etc are all valid matches</li>
<li>Note that <code>i</code> doesn't affect the case of the replacement string</li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/change.txt.html#%3As_flags">:h s_flags</a> for a complete list of flags and more details about them.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/gdcfU8wTMrM" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 26: removing duplicate lines with GNU awk2023-04-18T00:00:00+00:002023-04-20T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-26/<p><code>awk '!a[$0]++'</code> is one of the most famous Awk one-liners. It eliminates line based duplicates while retaining input order. The following example shows it in action along with an illustration of how the logic works.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat purchases.txt
</span><span>coffee
</span><span>tea
</span><span>washing powder
</span><span>coffee
</span><span>toothpaste
</span><span>tea
</span><span>soap
</span><span>tea
</span><span>
</span><span>$ awk </span><span style="color:#d07711;">'{print +a[$0] "\t" $0; a[$0]++}'</span><span> purchases.txt
</span><span style="color:#b3933a;">0</span><span> coffee
</span><span style="color:#b3933a;">0</span><span> tea
</span><span style="color:#b3933a;">0</span><span> washing powder
</span><span style="color:#b3933a;">1</span><span> coffee
</span><span style="color:#b3933a;">0</span><span> toothpaste
</span><span style="color:#b3933a;">1</span><span> tea
</span><span style="color:#b3933a;">0</span><span> soap
</span><span style="color:#b3933a;">2</span><span> tea
</span><span>
</span><span style="color:#7f8989;"># only those entries with zero in the first column will be retained
</span><span>$ awk </span><span style="color:#d07711;">'!a[$0]++'</span><span> purchases.txt
</span><span>coffee
</span><span>tea
</span><span>washing powder
</span><span>toothpaste
</span><span>soap
</span></code></pre>
<p>Removing field based duplicates is simple for single field comparison. Just change <code>$0</code> to the required field number after setting the appropriate field separator.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat duplicates.txt
</span><span>brown,toy,bread,</span><span style="color:#b3933a;">42
</span><span>dark red,ruby,rose,</span><span style="color:#b3933a;">111
</span><span>blue,ruby,water,</span><span style="color:#b3933a;">333
</span><span>dark red,sky,rose,</span><span style="color:#b3933a;">555
</span><span>yellow,toy,flower,</span><span style="color:#b3933a;">333
</span><span>white,sky,bread,</span><span style="color:#b3933a;">111
</span><span>light red,purse,rose,</span><span style="color:#b3933a;">333
</span><span>
</span><span style="color:#7f8989;"># based on the last field
</span><span>$ awk </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">F</span><span>, </span><span style="color:#d07711;">'!seen[$NF]++'</span><span> duplicates.txt
</span><span>brown,toy,bread,</span><span style="color:#b3933a;">42
</span><span>dark red,ruby,rose,</span><span style="color:#b3933a;">111
</span><span>blue,ruby,water,</span><span style="color:#b3933a;">333
</span><span>dark red,sky,rose,</span><span style="color:#b3933a;">555
</span></code></pre>
<p>For multiple fields comparison, separate the fields with <code>,</code> so that <code>SUBSEP</code> is used to combine the field values to generate the key. <code>SUBSEP</code> has a default value of <code>\034</code> which is a non-printing character and not usually used in text files.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># based on the first and third fields
</span><span>$ awk </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">F</span><span>, </span><span style="color:#d07711;">'!seen[$1,$3]++'</span><span> duplicates.txt
</span><span>brown,toy,bread,</span><span style="color:#b3933a;">42
</span><span>dark red,ruby,rose,</span><span style="color:#b3933a;">111
</span><span>blue,ruby,water,</span><span style="color:#b3933a;">333
</span><span>yellow,toy,flower,</span><span style="color:#b3933a;">333
</span><span>white,sky,bread,</span><span style="color:#b3933a;">111
</span><span>light red,purse,rose,</span><span style="color:#b3933a;">333
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> <a href="https://github.com/koraa/huniq">huniq</a> is a faster alternative for removing line based duplicates.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/nfQn6IkxxeU" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/learn_gnuawk">CLI text processing with GNU awk</a> ebook.</p>
Python tip 22: possessive quantifiers2023-04-13T00:00:00+00:002023-04-13T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-22/<p>Until Python 3.10, you had to use alternatives like the third-party <a href="https://pypi.org/project/regex/">regex module</a> for possessive quantifiers and <a href="https://learnbyexample.github.io/tips/python-tip-26/">atomic grouping</a>. The <code>re</code> module supports these features from Python 3.11 version.</p>
<p>Greedy quantifiers will match as much as possible but will backtrack to help the overall pattern to succeed. Possessive quantifiers behave like greedy but won't backtrack.</p>
<p>Suppose you want to match integer numbers greater than or equal to <code>100</code> where these numbers can optionally have leading zeros.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>numbers </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'42 314 001 12 00984'
</span><span>
</span><span style="color:#7f8989;"># this solution fails because 0* and \d{3,} can both match leading zeros
</span><span style="color:#7f8989;"># and greedy quantifiers will give up characters to help overall regex succeed
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">0</span><span style="color:#72ab00;">*</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">{3,}</span><span style="color:#d07711;">'</span><span>, numbers)
</span><span>[</span><span style="color:#d07711;">'314'</span><span>, </span><span style="color:#d07711;">'001'</span><span>, </span><span style="color:#d07711;">'00984'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># here 0*+ will not give back leading zeros after they are consumed
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">0</span><span style="color:#72ab00;">*+</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">{3,}</span><span style="color:#d07711;">'</span><span>, numbers)
</span><span>[</span><span style="color:#d07711;">'314'</span><span>, </span><span style="color:#d07711;">'00984'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># workaround if possessive quantifiers are not supported
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">0</span><span style="color:#72ab00;">*</span><span style="color:#aeb52b;">[</span><span style="color:#b3933a;">1-9</span><span style="color:#aeb52b;">]\d</span><span style="color:#72ab00;">{2,}</span><span style="color:#d07711;">'</span><span>, numbers)
</span><span>[</span><span style="color:#d07711;">'314'</span><span>, </span><span style="color:#d07711;">'00984'</span><span>]
</span></code></pre>
<p>Here's another example. The goal is to match lines whose first non-whitespace character is not a <code>#</code> character. A matching line should have at least one non-<code>#</code> character, so empty lines and those with only whitespace characters should not match.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>lines </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'#cmt'</span><span>, </span><span style="color:#d07711;">'c = "#"'</span><span>, </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\t</span><span style="color:#d07711;"> #comment'</span><span>, </span><span style="color:#d07711;">'abc'</span><span>, </span><span style="color:#d07711;">''</span><span>, </span><span style="color:#d07711;">' </span><span style="color:#aeb52b;">\t </span><span style="color:#d07711;">'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># this solution fails because \s* can backtrack
</span><span style="color:#7f8989;"># and [^#] can match a whitespace character as well
</span><span style="color:#72ab00;">>>> </span><span>[e </span><span style="color:#72ab00;">for </span><span>e </span><span style="color:#72ab00;">in </span><span>lines </span><span style="color:#72ab00;">if </span><span>re.</span><span style="color:#5597d6;">match</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\s</span><span style="color:#72ab00;">*</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">#]</span><span style="color:#d07711;">'</span><span>, e)]
</span><span>[</span><span style="color:#d07711;">'c = "#"'</span><span>, </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\t</span><span style="color:#d07711;"> #comment'</span><span>, </span><span style="color:#d07711;">'abc'</span><span>, </span><span style="color:#d07711;">' </span><span style="color:#aeb52b;">\t </span><span style="color:#d07711;">'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># this works because \s*+ will not give back any whitespace characters
</span><span style="color:#72ab00;">>>> </span><span>[e </span><span style="color:#72ab00;">for </span><span>e </span><span style="color:#72ab00;">in </span><span>lines </span><span style="color:#72ab00;">if </span><span>re.</span><span style="color:#5597d6;">match</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\s</span><span style="color:#72ab00;">*+</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">#]</span><span style="color:#d07711;">'</span><span>, e)]
</span><span>[</span><span style="color:#d07711;">'c = "#"'</span><span>, </span><span style="color:#d07711;">'abc'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># workaround if possessive quantifiers are not supported
</span><span style="color:#72ab00;">>>> </span><span>[e </span><span style="color:#72ab00;">for </span><span>e </span><span style="color:#72ab00;">in </span><span>lines </span><span style="color:#72ab00;">if </span><span>re.</span><span style="color:#5597d6;">match</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\s</span><span style="color:#72ab00;">*</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">#</span><span style="color:#b3933a;">\s</span><span style="color:#aeb52b;">]</span><span style="color:#d07711;">'</span><span>, e)]
</span><span>[</span><span style="color:#d07711;">'c = "#"'</span><span>, </span><span style="color:#d07711;">'abc'</span><span>]
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See my blog post on <a href="https://learnbyexample.github.io/python-regex-possessive-quantifier/">possessive quantifiers and atomic grouping</a> for more examples, details about catastrophic backtracking and so on.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/Y7XuZOLdG0o" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> and <a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> ebooks.</p>
Python tip 26: atomic grouping2023-04-13T00:00:00+00:002023-04-13T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-26/<p>Until Python 3.10, you had to use alternatives like the third-party <a href="https://pypi.org/project/regex/">regex module</a> for <a href="https://learnbyexample.github.io/tips/python-tip-22/">possessive quantifiers</a> and atomic grouping. The <code>re</code> module supports these features from Python 3.11 version.</p>
<p>Greedy and non-greedy quantifiers will backtrack to help the overall pattern to succeed. The syntax for an atomic group is <code>(?>pat)</code>, where <code>pat</code> is the pattern you want to safeguard from further backtracking. You can think of it as a special group that is isolated from the other parts of the regular expression.</p>
<p>Here's an example with greedy quantifier:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> import </span><span>re
</span><span style="color:#72ab00;">>>> </span><span>numbers </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'42 314 001 12 00984'
</span><span>
</span><span style="color:#7f8989;"># 0* is greedy and the (?>) grouping prevents backtracking
</span><span style="color:#7f8989;"># same as: re.findall(r'0*+\d{3,}', numbers)
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#72ab00;">?</span><span style="color:#7c8f4c;">>0</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">)</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">{3,}</span><span style="color:#d07711;">'</span><span>, numbers)
</span><span>[</span><span style="color:#d07711;">'314'</span><span>, </span><span style="color:#d07711;">'00984'</span><span>]
</span></code></pre>
<p>Here's an example with non-greedy quantifier:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>ip </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'fig::mango::pineapple::guava::apples::orange'
</span><span>
</span><span style="color:#7f8989;"># this matches from the first '::' to the first occurrence of '::apple'
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">::</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#7c8f4c;">::apple</span><span style="color:#d07711;">'</span><span>, ip)[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#d07711;">'::mango::pineapple::guava::apple'
</span><span>
</span><span style="color:#7f8989;"># '(?>::.*?::)' will match only from '::' to the very next '::'
</span><span style="color:#7f8989;"># '::mango::' fails because 'apple' isn't found afterwards
</span><span style="color:#7f8989;"># similarly '::pineapple::' fails
</span><span style="color:#7f8989;"># '::guava::' succeeds because it is followed by 'apple'
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#72ab00;">?</span><span style="color:#7c8f4c;">>::</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#7c8f4c;">::)apple</span><span style="color:#d07711;">'</span><span>, ip)[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#d07711;">'::guava::apple'
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/DQfcST_XN_E" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> and <a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> ebooks.</p>
Vim tip 24: movement commands within the current file2023-04-04T00:00:00+00:002023-04-04T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-24/<p>Here are some commands you can use in Normal mode to move within the current file:</p>
<ul>
<li><kbd>gg</kbd> move to the first non-blank character of the first line</li>
<li><kbd>G</kbd> move to the first non-blank character of the last line</li>
<li><kbd>5G</kbd> move to the first non-blank character of the fifth line
<ul>
<li>As an alternative, you can use <kbd>:5</kbd> followed by <kbd>Enter</kbd> key (Command-line mode)</li>
</ul>
</li>
<li><kbd>50%</kbd> move to the halfway point
<ul>
<li>you can use other percentages as needed</li>
</ul>
</li>
<li><kbd>%</kbd> move to matching pair of brackets like <code>()</code>, <code>{}</code> and <code>[]</code>
<ul>
<li>This will work across lines and nesting is taken into consideration as well</li>
<li>If the cursor is on a non-bracket character and a bracket character is present later in the line, the <kbd>%</kbd> command will move to the matching pair of that character (which could be present in some other line too)</li>
<li>Use the <code>matchpairs</code> option to customize the matching pairs. For example, <kbd>:set matchpairs+=<:></kbd> will match <code><></code> as well</li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> It is also possible to match a pair of keywords like HTML tags, if-else, etc with <kbd>%</kbd>. See <a href="https://vimhelp.org/usr_05.txt.html#05.5">:h matchit-install</a> for details.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/yMAbE8BrehE" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 25: get file properties using the stat command2023-03-28T00:00:00+00:002023-03-28T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-25/<p>The <code>stat</code> command is useful to get details like file type, size, inode, permissions, last accessed and modified timestamps, etc. You'll get all of these details by default. The <code>-c</code> and <code>--printf</code> options can be used to display only the required details in a particular format.</p>
<p>Here's an example to get accessed and modified timestamps of a file:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># sample directory and sample file
</span><span>$ mkdir stat_examples </span><span style="color:#72ab00;">&&</span><span> cd </span><span style="color:#5597d6;">$_
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'long\nshot\n' </span><span style="color:#72ab00;">></span><span> ip.txt
</span><span>
</span><span style="color:#7f8989;"># %x gives the last accessed timestamp
</span><span>$ stat </span><span style="color:#72ab00;">-</span><span>c </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%x</span><span style="color:#d07711;">'</span><span> ip.txt
</span><span style="color:#b3933a;">2023</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">03</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">27 20</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">20</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">55.217530670 </span><span style="color:#72ab00;">+</span><span style="color:#b3933a;">0530
</span><span>
</span><span style="color:#7f8989;"># modify the file
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'apple\nbanana\n' </span><span style="color:#72ab00;">>></span><span> ip.txt
</span><span style="color:#7f8989;"># %y gives the last modified timestamp
</span><span>$ stat </span><span style="color:#72ab00;">-</span><span>c </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%y</span><span style="color:#d07711;">'</span><span> ip.txt
</span><span style="color:#b3933a;">2023</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">03</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">27 20</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">21</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">50.298964283 </span><span style="color:#72ab00;">+</span><span style="color:#b3933a;">0530
</span></code></pre>
<p>Here's an example with some more file properties:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># %s gives file size in bytes
</span><span style="color:#7f8989;"># \n is used to insert a newline
</span><span style="color:#7f8989;"># %i gives the inode value
</span><span style="color:#7f8989;"># same as: stat --printf='%s\n%i\n' ip.txt
</span><span>$ stat </span><span style="color:#72ab00;">-</span><span>c </span><span style="color:#5597d6;">$'</span><span style="color:#d07711;">%s\n%i' ip.txt
</span><span style="color:#d07711;">23
</span><span style="color:#d07711;">6438890
</span></code></pre>
<p>Here's an example for a linked file:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ ln </span><span style="color:#72ab00;">-</span><span>s </span><span style="color:#72ab00;">/</span><span>usr</span><span style="color:#72ab00;">/</span><span>share</span><span style="color:#72ab00;">/</span><span>dict</span><span style="color:#72ab00;">/</span><span>words words.txt
</span><span>
</span><span style="color:#7f8989;"># %N gives quoted filenames
</span><span style="color:#7f8989;"># if input is a link, path it points to is also displayed
</span><span>$ stat </span><span style="color:#72ab00;">-</span><span>c </span><span style="color:#d07711;">'%N'</span><span> words.txt
</span><span style="color:#d07711;">'words.txt' </span><span style="color:#72ab00;">-> </span><span style="color:#d07711;">'/usr/share/dict/words'
</span></code></pre>
<p>You can also pass multiple file arguments:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'#!/bin/bash\n\necho hi\n' </span><span style="color:#72ab00;">></span><span> hi.sh
</span><span>
</span><span style="color:#7f8989;"># %s gives file size in bytes
</span><span style="color:#7f8989;"># %n gives filenames
</span><span>$ stat </span><span style="color:#72ab00;">-</span><span>c </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%s %n</span><span style="color:#d07711;">'</span><span> ip.txt hi.sh
</span><span style="color:#b3933a;">23</span><span> ip.txt
</span><span style="color:#b3933a;">21</span><span> hi.sh
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> <img src="/images/warning.svg" alt="warning" /> The <code>stat</code> command should be preferred instead of parsing <code>ls -l</code> output for file details. See <a href="https://mywiki.wooledge.org/ParsingLs">mywiki.wooledge: avoid parsing output of ls</a> and <a href="https://unix.stackexchange.com/q/128985/109046">unix.stackexchange: why not parse ls?</a> for explanation and other alternatives.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/wEddnyjLfRA" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/cli-computing">Linux Command Line Computing</a> ebook.</p>
Python tip 25: split and partition string methods2023-03-21T00:00:00+00:002023-03-21T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-25/<p>The <code>split()</code> method splits a string based on the given substring and returns a <code>list</code>. By default, whitespace is used for splitting and empty elements are discarded.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>greeting </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">' </span><span style="color:#aeb52b;">\t\r\n</span><span style="color:#d07711;"> have a nice </span><span style="color:#aeb52b;">\r\v\t</span><span style="color:#d07711;"> day </span><span style="color:#aeb52b;">\f\v\r\t\n </span><span style="color:#d07711;">'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>greeting.</span><span style="color:#5597d6;">split</span><span>()
</span><span>[</span><span style="color:#d07711;">'have'</span><span>, </span><span style="color:#d07711;">'a'</span><span>, </span><span style="color:#d07711;">'nice'</span><span>, </span><span style="color:#d07711;">'day'</span><span>]
</span></code></pre>
<p>You can split the input based on a specific string literal by passing it as an argument. Here are some examples:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>creatures </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'dragon][unicorn][centaur'
</span><span style="color:#72ab00;">>>> </span><span>creatures.</span><span style="color:#5597d6;">split</span><span>(</span><span style="color:#d07711;">']['</span><span>)
</span><span>[</span><span style="color:#d07711;">'dragon'</span><span>, </span><span style="color:#d07711;">'unicorn'</span><span>, </span><span style="color:#d07711;">'centaur'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># empty elements will be preserved in this case
</span><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">':car::jeep::'</span><span>.</span><span style="color:#5597d6;">split</span><span>(</span><span style="color:#d07711;">':'</span><span>)
</span><span>[</span><span style="color:#d07711;">''</span><span>, </span><span style="color:#d07711;">'car'</span><span>, </span><span style="color:#d07711;">''</span><span>, </span><span style="color:#d07711;">'jeep'</span><span>, </span><span style="color:#d07711;">''</span><span>, </span><span style="color:#d07711;">''</span><span>]
</span></code></pre>
<p>The <code>maxsplit</code> argument allows you to restrict the number of times the input string should be split. Use <code>rsplit()</code> if you want to split from right to left.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># split once
</span><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">'apple-grape-mango-fig'</span><span>.</span><span style="color:#5597d6;">split</span><span>(</span><span style="color:#d07711;">'-'</span><span>, </span><span style="color:#5597d6;">maxsplit</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">1</span><span>)
</span><span>[</span><span style="color:#d07711;">'apple'</span><span>, </span><span style="color:#d07711;">'grape-mango-fig'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># match the rightmost occurrence
</span><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">'apple-grape-mango-fig'</span><span>.</span><span style="color:#5597d6;">rsplit</span><span>(</span><span style="color:#d07711;">'-'</span><span>, </span><span style="color:#5597d6;">maxsplit</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">1</span><span>)
</span><span>[</span><span style="color:#d07711;">'apple-grape-mango'</span><span>, </span><span style="color:#d07711;">'fig'</span><span>]
</span><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">'apple-grape-mango-fig'</span><span>.</span><span style="color:#5597d6;">rsplit</span><span>(</span><span style="color:#d07711;">'-'</span><span>, </span><span style="color:#5597d6;">maxsplit</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">2</span><span>)
</span><span>[</span><span style="color:#d07711;">'apple-grape'</span><span>, </span><span style="color:#d07711;">'mango'</span><span>, </span><span style="color:#d07711;">'fig'</span><span>]
</span></code></pre>
<p>The <code>partition()</code> method will give a <code>tuple</code> of three elements — portion before the leftmost match, the separator itself and the portion after the split. You can use <code>rpartition()</code> to match the rightmost occurrence of the separator.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>marks </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'maths:85'
</span><span style="color:#72ab00;">>>> </span><span>marks.</span><span style="color:#5597d6;">partition</span><span>(</span><span style="color:#d07711;">':'</span><span>)
</span><span>(</span><span style="color:#d07711;">'maths'</span><span>, </span><span style="color:#d07711;">':'</span><span>, </span><span style="color:#d07711;">'85'</span><span>)
</span><span>
</span><span style="color:#7f8989;"># last two elements will be empty if there is no match
</span><span style="color:#72ab00;">>>> </span><span>marks.</span><span style="color:#5597d6;">partition</span><span>(</span><span style="color:#d07711;">'='</span><span>)
</span><span>(</span><span style="color:#d07711;">'maths:85'</span><span>, </span><span style="color:#d07711;">''</span><span>, </span><span style="color:#d07711;">''</span><span>)
</span><span>
</span><span style="color:#7f8989;"># match the rightmost occurrence
</span><span style="color:#72ab00;">>>> </span><span>creatures </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'dragon][unicorn][centaur'
</span><span style="color:#72ab00;">>>> </span><span>creatures.</span><span style="color:#5597d6;">rpartition</span><span>(</span><span style="color:#d07711;">']['</span><span>)
</span><span>(</span><span style="color:#d07711;">'dragon][unicorn'</span><span>, </span><span style="color:#d07711;">']['</span><span>, </span><span style="color:#d07711;">'centaur'</span><span>)
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://learnbyexample.github.io/py_regular_expressions/dot-metacharacter-and-quantifiers.html#resplit">Understanding Python re(gex)?</a> ebook to learn about string splitting with regular expressions.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/tb_ioYxXwsA" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
100+ Interactive Python Regex Exercises2023-03-20T00:00:00+00:002025-01-09T00:00:00+00:00https://learnbyexample.github.io/interactive-python-regex-exercises/<p>Having an interactive program that automatically loads questions and checks the solution is wonderful to have while learning a topic. This <a href="https://github.com/learnbyexample/TUI-apps/blob/main/PyRegexExercises">TUI app</a> has beginner to advanced level exercises for Python regular expressions. There are more than 100 exercises covering both the builtin <code>re</code> and third-party <code>regex</code> module.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PyRegexExercises/pyregex_exercises.png" alt="Sample screenshot for Python regex exercises" loading="lazy" /></p>
<span id="continue-reading"></span><br>
<h2 id="installation">Installation<a class="zola-anchor" href="#installation" aria-label="Anchor link for: installation">🔗</a></h2>
<p>This app is available on PyPI as <a href="https://pypi.org/project/regexexercises/">regexexercises</a>. Example installation instructions are shown below, adjust them based on your preferences and OS.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># virtual environment
</span><span style="color:#5597d6;">$</span><span> python3</span><span style="color:#5597d6;"> -m</span><span> venv textual_apps
</span><span style="color:#5597d6;">$</span><span> cd textual_apps
</span><span style="color:#5597d6;">$</span><span> source bin/activate
</span><span style="color:#5597d6;">$</span><span> pip install regexexercises
</span><span>
</span><span style="color:#7f8989;"># launch the app
</span><span style="color:#5597d6;">$</span><span> regexexercises
</span></code></pre>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> If you are on Windows, using the <a href="https://en.wikipedia.org/wiki/Windows_Terminal">Windows Terminal</a> is recommended. See <a href="https://github.com/learnbyexample/TUI-apps/issues/3#issuecomment-1481488042">this issue</a> for Virtual Environment commands and other details.</p>
</blockquote>
<p>To run the app without having to enter the virtual environment again, add this alias to <code>.bashrc</code> (or equivalent):</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># you'll have to change the path
</span><span style="color:#b39f04;">alias </span><span style="color:#c23f31;">regexexercises</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'/path/to/textual_apps/bin/regexexercises'
</span></code></pre>
<p>As an alternative to manually managing such virtual environments, you can use <a href="https://github.com/pypa/pipx">https://github.com/pypa/pipx</a> instead:</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> pipx install regexexercises
</span><span style="color:#5597d6;">$</span><span> regexexercises
</span></code></pre>
<p>As yet another alternative, you can install <code>textual==0.85.2</code> (see <a href="https://textual.textualize.io/getting_started/">Textual documentation</a> for more details), clone my <a href="https://github.com/learnbyexample/TUI-apps">TUI-apps</a> repository and run the <code>pyregex_exercises.py</code> file.</p>
<p>Adjust the terminal dimensions for the widgets to appear properly, for example 84x25 (characters x lines).</p>
<br>
<h2 id="video-demo">Video demo<a class="zola-anchor" href="#video-demo" aria-label="Anchor link for: video-demo">🔗</a></h2>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/0oXPeF8HutQ" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<h2 id="brief-guide">Brief Guide<a class="zola-anchor" href="#brief-guide" aria-label="Anchor link for: brief-guide">🔗</a></h2>
<ul>
<li>Type your solution in the input box below the question.
<ul>
<li>Use <code>ip</code> variable to represent the sample input.</li>
<li>Any single valid Python expression will be accepted.</li>
<li>Some basic readline-like shortcuts are supported, for example <strong>Ctrl+u</strong>, <strong>Ctrl+k</strong>, <strong>Ctrl+w</strong>, etc</li>
</ul>
</li>
<li>Press <strong>Enter</strong> to execute the code.
<ul>
<li>Output would be displayed below the command box.</li>
<li>If the output matches the expected results, the solution box will turn <em>green</em> and a reference solution will also be shown.</li>
<li>Error messages due to exceptions will be displayed in <em>red</em>.</li>
</ul>
</li>
<li>Press <strong>Ctrl+p</strong> and <strong>Ctrl+n</strong> to navigate the questions list.</li>
<li>Press <strong>Ctrl+r</strong> to toggle between <strong>str</strong> and <strong>repr</strong> — helps to spot characters like tabs, newlines, backspaces, etc.</li>
<li>Press <strong>Ctrl+b</strong> to toggle between <strong>expected</strong> and <strong>actual</strong> — helps to debug incorrect solutions.</li>
<li>Press <strong>Ctrl+s</strong> toggle reference solution</li>
<li>Press <strong>Ctrl+t</strong> to toggle between light and dark themes.</li>
<li>Press <strong>Ctrl+q</strong> to quit the app.</li>
<li>Press <strong>F1</strong> to view a detailed guide within the app itself and press <strong>F2</strong> to get back to the exercises.</li>
</ul>
<p>Your progress will be automatically saved and restored. Already answered questions will be skipped.</p>
<blockquote>
<p><img src="/images/warning.svg" alt="warning" /> There is no safeguard against the code you are executing. They are treated as if you executed them from a Python program.</p>
</blockquote>
<p>See <a href="https://github.com/learnbyexample/TUI-apps/blob/main/PyRegexExercises/app_guide.md">app_guide.md</a> for more detailed instructions.</p>
<br>
<h2 id="ebook">Ebook<a class="zola-anchor" href="#ebook" aria-label="Anchor link for: ebook">🔗</a></h2>
<p>See my <a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> ebook to learn regular expressions with hundreds of examples and exercises.</p>
<br>
<h2 id="feedback">Feedback<a class="zola-anchor" href="#feedback" aria-label="Anchor link for: feedback">🔗</a></h2>
<p>I'd highly appreciate your feedback. Please file <a href="https://github.com/learnbyexample/TUI-apps/issues">an issue</a> if there are bugs, crashes, etc.</p>
<p>Hope you find this TUI app useful. Happy learning :)</p>
Vim tip 23: editing lines filtered by a pattern2023-03-14T00:00:00+00:002023-03-14T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-23/<p>The syntax for <code>g</code> command (short for <code>global</code>) is shown below:</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>:[range]g[lobal]/{pattern}/[cmd]
</span></code></pre>
<p>This command is used to edit lines that are first filtered based on a <code>searchpattern</code>.</p>
<ul>
<li><kbd>:g/call/d</kbd> delete all lines containing <code>call</code>
<ul>
<li>similar to the <code>d</code> Normal mode command, the deleted contents will be saved to the default <code>"</code> register</li>
<li><kbd>:g/call/d a</kbd> in addition to the default register, the deleted content will also be stored in the <code>"a</code> register</li>
<li><kbd>:g/call/d _</kbd> deleted content won't be saved anywhere, since it uses the black hole register</li>
</ul>
</li>
<li><kbd>:g/^#/t0</kbd> copy all lines starting with <code>#</code> to the start of the file</li>
<li><kbd>:1,5 g/call/d</kbd> delete all lines containing <code>call</code> only for the first five lines</li>
<li><kbd>:g/cat/ s/animal/mammal/g</kbd> replace <code>animal</code> with <code>mammal</code> only for the lines containing <code>cat</code></li>
<li><kbd>:.,.+20 g/^#/ normal >></kbd> indent the current line and the next <code>20</code> lines only if the line starts with <code>#</code>
<ul>
<li>Note the use of <code>normal</code> when you need to use Normal mode commands on the filtered lines</li>
<li>Use <code>normal!</code> if you don't want user defined mappings to be considered</li>
</ul>
</li>
</ul>
<p>You can use <code>g!</code> or <code>v</code> to act on lines <em>not</em> satisfying the filtering condition.</p>
<ul>
<li><kbd>:v/jump/d</kbd> delete all lines <em>not</em> containing <code>jump</code>
<ul>
<li>same as <kbd>:g!/jump/d</kbd></li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> In addition to the <code>/</code> delimiter, you can also use any single byte character other than alphabets, <code>\</code>, <code>"</code> or <code>|</code>.</p>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/repeat.txt.html#%3Aglobal">:h :g</a> for more details.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/uQKaAOKgr2o" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
Python Regular Expressions Playground2023-03-11T00:00:00+00:002025-01-09T00:00:00+00:00https://learnbyexample.github.io/python-regex-playground/<p>This TUI application is intended as an interactive playground for Python Regular Expressions. The app also includes a comprehensive cheatsheet and several interactive examples.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PyRegexPlayground/pyregex_finditer.png" alt="Sample screenshot from the Playground screen" loading="lazy" /></p>
<span id="continue-reading"></span><br>
<h2 id="installation">Installation<a class="zola-anchor" href="#installation" aria-label="Anchor link for: installation">🔗</a></h2>
<p>This app is available on PyPI as <a href="https://pypi.org/project/regexplayground/">regexplayground</a>. Example installation instructions are shown below, adjust them based on your preferences and OS.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># virtual environment
</span><span style="color:#5597d6;">$</span><span> python3</span><span style="color:#5597d6;"> -m</span><span> venv textual_apps
</span><span style="color:#5597d6;">$</span><span> cd textual_apps
</span><span style="color:#5597d6;">$</span><span> source bin/activate
</span><span style="color:#5597d6;">$</span><span> pip install regexplayground
</span><span>
</span><span style="color:#7f8989;"># launch the app
</span><span style="color:#5597d6;">$</span><span> regexplayground
</span></code></pre>
<p>To run the app without having to enter the virtual environment again, add this alias to <code>.bashrc</code> (or equivalent):</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># you'll have to change the path
</span><span style="color:#b39f04;">alias </span><span style="color:#c23f31;">regexplayground</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'/path/to/textual_apps/bin/regexplayground'
</span></code></pre>
<p>As an alternative, you can install <code>textual==0.85.2</code> (see <a href="https://textual.textualize.io/getting_started/">Textual documentation</a> for more details), clone my <a href="https://github.com/learnbyexample/TUI-apps">TUI-apps repository</a> and run the <code>pyregex_playground.py</code> file.</p>
<p>Adjust the terminal dimensions for the widgets to appear properly, for example 84x25 (characters x lines). Here's another screenshot:</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PyRegexPlayground/pyregex_examples.png" alt="Sample screenshot from the Interactive Examples screen" loading="lazy" /></p>
<br>
<h2 id="brief-guide">Brief Guide<a class="zola-anchor" href="#brief-guide" aria-label="Anchor link for: brief-guide">🔗</a></h2>
<p>You can type the search pattern in the <strong>Compile</strong> input box and press the <strong>Enter</strong> key (or <strong>Ctrl+r</strong>) to execute. For example, <code>re.compile(r'\d')</code> to match digit characters. Matching portions will be highlighted in red.</p>
<p>The compiled pattern is available via the <code>pat</code> variable and you can use <code>ip</code> to refer to the input string. You can transform or extract data by typing appropriate expression in the <strong>Action</strong> box. For example, <code>pat.sub(r'(\g<0>)', ip)</code> will add parenthesis around the matching portions.</p>
<p>You can skip the Compile box and directly use the Action box too. For example, <code>[m.span() for m in re.finditer(r'\d+', ip)]</code> to get the location of all the matching portions.</p>
<blockquote>
<p><img src="/images/warning.svg" alt="warning" /> There is no safeguard against the commands you have typed. They are treated as if you executed them from a Python program.</p>
</blockquote>
<p>Press <strong>F1</strong> to view the detailed guide from within the app, <strong>F2</strong> to get back to the Playground from other screens, <strong>F3</strong> to view a cheatsheet and <strong>F4</strong> for interactive examples.</p>
<p>For more detailed instructions, see <a href="https://github.com/learnbyexample/TUI-apps/blob/main/PyRegexPlayground/app_guide.md">app guide</a>.</p>
<br>
<h2 id="ebook">Ebook<a class="zola-anchor" href="#ebook" aria-label="Anchor link for: ebook">🔗</a></h2>
<p>See my <a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> ebook to learn regular expressions with hundreds of examples and exercises.</p>
<br>
<h2 id="feedback">Feedback<a class="zola-anchor" href="#feedback" aria-label="Anchor link for: feedback">🔗</a></h2>
<p>I'd highly appreciate your feedback. Please file <a href="https://github.com/learnbyexample/TUI-apps/issues">an issue</a> if there are bugs, crashes, etc.</p>
<p>Hope you find this TUI app useful. Happy learning :)</p>
CLI tip 24: inserting file contents one line at a time2023-03-07T00:00:00+00:002023-03-07T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-24/<p>The <code>R</code> command provided by <code>GNU sed</code> is very similar to <code>r</code> with respect to most of the rules seen in an earlier <a href="https://learnbyexample.github.io/tips/cli-tip-18/">tip</a>. But instead of reading entire file contents, <code>R</code> will read one line at a time from the source file when the given address matches. If entire file has already been read and another address matches, <code>sed</code> will proceed as if the line was empty.</p>
<p>Here's an example:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat ip.txt
</span><span> </span><span style="color:#72ab00;">*</span><span> sky
</span><span> </span><span style="color:#72ab00;">*</span><span> apple
</span><span>$ cat fav_colors.txt
</span><span>deep red
</span><span>yellow
</span><span>reddish
</span><span>brown
</span><span>
</span><span style="color:#7f8989;"># add a line from 'ip.txt'
</span><span style="color:#7f8989;"># whenever a line from 'fav_colors.txt' contains 'red'
</span><span>$ sed </span><span style="color:#d07711;">'/red/R ip.txt'</span><span> fav_colors.txt
</span><span>deep red
</span><span> </span><span style="color:#72ab00;">*</span><span> sky
</span><span>yellow
</span><span>reddish
</span><span> </span><span style="color:#72ab00;">*</span><span> apple
</span><span>brown
</span></code></pre>
<p>You can combine with other <code>sed</code> commands to solve various kind of problems. For example, to replace the matching lines:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># empty // will refer to the previously used regex, /red/ in this case
</span><span>$ sed </span><span style="color:#72ab00;">-</span><span>e </span><span style="color:#d07711;">'/red/R ip.txt' </span><span style="color:#72ab00;">-</span><span>e </span><span style="color:#d07711;">'//d'</span><span> fav_colors.txt
</span><span> </span><span style="color:#72ab00;">*</span><span> sky
</span><span>yellow
</span><span> </span><span style="color:#72ab00;">*</span><span> apple
</span><span>brown
</span></code></pre>
<p>And, here's how you can interleave contents of two files:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># /dev/stdin will get data from stdin (output of 'seq 4' here)
</span><span style="color:#7f8989;"># same as: seq 4 | paste -d'\n' fav_colors.txt -
</span><span>$ seq </span><span style="color:#b3933a;">4 </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'R /dev/stdin'</span><span> fav_colors.txt
</span><span>deep red
</span><span style="color:#b3933a;">1
</span><span>yellow
</span><span style="color:#b3933a;">2
</span><span>reddish
</span><span style="color:#b3933a;">3
</span><span>brown
</span><span style="color:#b3933a;">4
</span><span>
</span><span style="color:#7f8989;"># using 'paste' here will add a newline when stdin runs out of data
</span><span>$ seq </span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'R /dev/stdin'</span><span> fav_colors.txt
</span><span>deep red
</span><span style="color:#b3933a;">1
</span><span>yellow
</span><span style="color:#b3933a;">2
</span><span>reddish
</span><span>brown
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/W7LvN7X6Rfg" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/learn_gnused">CLI text processing with GNU sed</a> ebook.</p>
Python tip 24: modifying list using insert and slice2023-02-28T00:00:00+00:002023-02-28T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-24/<p>The <code>insert()</code> list method helps to insert an object before the given index. Negative indexing is also supported.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>books </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'Sourdough'</span><span>, </span><span style="color:#d07711;">'Sherlock Holmes'</span><span>, </span><span style="color:#d07711;">'Cradle'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># same as: books.insert(-1, 'The Martian')
</span><span style="color:#72ab00;">>>> </span><span>books.</span><span style="color:#5597d6;">insert</span><span>(</span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#d07711;">'The Martian'</span><span>)
</span><span style="color:#72ab00;">>>> </span><span>books
</span><span>[</span><span style="color:#d07711;">'Sourdough'</span><span>, </span><span style="color:#d07711;">'Sherlock Holmes'</span><span>, </span><span style="color:#d07711;">'The Martian'</span><span>, </span><span style="color:#d07711;">'Cradle'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># index >= list-length will append the object at the end
</span><span style="color:#72ab00;">>>> </span><span>books.</span><span style="color:#5597d6;">insert</span><span>(</span><span style="color:#b3933a;">1000</span><span>, </span><span style="color:#d07711;">'Legends & Lattes'</span><span>)
</span><span style="color:#72ab00;">>>> </span><span>books
</span><span>[</span><span style="color:#d07711;">'Sourdough'</span><span>, </span><span style="color:#d07711;">'Sherlock Holmes'</span><span>, </span><span style="color:#d07711;">'The Martian'</span><span>, </span><span style="color:#d07711;">'Cradle'</span><span>, </span><span style="color:#d07711;">'Legends & Lattes'</span><span>]
</span></code></pre>
<p>You can use slicing notation to modify one or more list elements. The list will automatically shrink or expand as needed. Here are some examples:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>nums </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">4</span><span>, </span><span style="color:#b3933a;">6</span><span>, </span><span style="color:#b3933a;">22</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">5</span><span>]
</span><span>
</span><span style="color:#7f8989;"># modify a single element
</span><span style="color:#72ab00;">>>> </span><span>nums[</span><span style="color:#b3933a;">0</span><span>] </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">100
</span><span style="color:#72ab00;">>>> </span><span>nums
</span><span>[</span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#b3933a;">4</span><span>, </span><span style="color:#b3933a;">6</span><span>, </span><span style="color:#b3933a;">22</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">5</span><span>]
</span><span>
</span><span style="color:#7f8989;"># modify the last three elements
</span><span style="color:#72ab00;">>>> </span><span>nums[</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">3</span><span>:] </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">3</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>nums
</span><span>[</span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#b3933a;">4</span><span>, </span><span style="color:#b3933a;">6</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">3</span><span>]
</span><span>
</span><span style="color:#7f8989;"># elements at index 1, 2 and 3 are replaced with a single object
</span><span style="color:#72ab00;">>>> </span><span>nums[</span><span style="color:#b3933a;">1</span><span>:</span><span style="color:#b3933a;">4</span><span>] </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#b3933a;">2000</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>nums
</span><span>[</span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#b3933a;">2000</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">3</span><span>]
</span><span>
</span><span style="color:#7f8989;"># element at index 1 is replaced with multiple elements
</span><span style="color:#72ab00;">>>> </span><span>nums[</span><span style="color:#b3933a;">1</span><span>:</span><span style="color:#b3933a;">2</span><span>] </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#b3933a;">3.14</span><span>, </span><span style="color:#b3933a;">4.13</span><span>, </span><span style="color:#b3933a;">6.78</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>nums
</span><span>[</span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#b3933a;">3.14</span><span>, </span><span style="color:#b3933a;">4.13</span><span>, </span><span style="color:#b3933a;">6.78</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">3</span><span>]
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> RHS must be an iterable when you use slicing notation with <code>:</code>, even when LHS refers to a single element. For example, <code>nums[1:2] = 100</code> is not valid.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/fyFX1nBCeS4" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
CLI tip 23: recursive filename matching with globstar2023-02-10T00:00:00+00:002023-02-10T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-23/<p>Enable the <code>globstar</code> option to recursively match filenames within a specified path. You can use <code>shopt -s globstar</code> and <code>shopt -u globstar</code> to set and unset this option respectively.</p>
<p>First, create some sample files:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ mkdir test_globstar </span><span style="color:#72ab00;">&&</span><span> cd </span><span style="color:#5597d6;">$_
</span><span>$ mkdir </span><span style="color:#72ab00;">-</span><span style="color:#b39f04;">p</span><span> todos projects/{tictactoe,calculator}
</span><span>$ touch ip.txt .hidden.txt report.log hello.py
</span><span>$ touch todos/{books,outing}.txt
</span><span>$ touch projects</span><span style="color:#72ab00;">/</span><span>tictactoe</span><span style="color:#72ab00;">/</span><span>game.py projects</span><span style="color:#72ab00;">/</span><span>calculator/{calc.sh,notes.txt}
</span></code></pre>
<p>Here are some examples:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ shopt </span><span style="color:#72ab00;">-</span><span>s globstar
</span><span>
</span><span>$ ls </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">1 </span><span style="color:#72ab00;">**</span><span>/</span><span style="color:#72ab00;">*</span><span>.txt
</span><span>ip.txt
</span><span>projects</span><span style="color:#72ab00;">/</span><span>calculator</span><span style="color:#72ab00;">/</span><span>notes.txt
</span><span>todos</span><span style="color:#72ab00;">/</span><span>books.txt
</span><span>todos</span><span style="color:#72ab00;">/</span><span>outing.txt
</span><span>
</span><span>$ ls </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">1 </span><span style="color:#72ab00;">**</span><span style="color:#c49a39;">/*/</span><span style="color:#72ab00;">*</span><span>.txt
</span><span>projects</span><span style="color:#72ab00;">/</span><span>calculator</span><span style="color:#72ab00;">/</span><span>notes.txt
</span><span>todos</span><span style="color:#72ab00;">/</span><span>books.txt
</span><span>todos</span><span style="color:#72ab00;">/</span><span>outing.txt
</span><span>
</span><span style="color:#7f8989;"># assumes extglob is enabled
</span><span>$ ls </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">1 </span><span style="color:#72ab00;">**</span><span>/</span><span style="color:#72ab00;">*</span><span>.@(py</span><span style="color:#72ab00;">|</span><span>sh)
</span><span>hello.py
</span><span>projects</span><span style="color:#72ab00;">/</span><span>calculator</span><span style="color:#72ab00;">/</span><span>calc.sh
</span><span>projects</span><span style="color:#72ab00;">/</span><span>tictactoe</span><span style="color:#72ab00;">/</span><span>game.py
</span><span>
</span><span>$ ls </span><span style="color:#72ab00;">-</span><span>1d </span><span style="color:#72ab00;">**/
</span><span>projects</span><span style="color:#72ab00;">/
</span><span>projects</span><span style="color:#72ab00;">/</span><span>calculator</span><span style="color:#72ab00;">/
</span><span>projects</span><span style="color:#72ab00;">/</span><span>tictactoe</span><span style="color:#72ab00;">/
</span><span>todos</span><span style="color:#72ab00;">/
</span></code></pre>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> If you need to match hidden files as well, enable the <code>dotglob</code> option:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ shopt </span><span style="color:#72ab00;">-</span><span>s dotglob
</span><span>$ ls </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">1 </span><span style="color:#72ab00;">**</span><span>/</span><span style="color:#72ab00;">*</span><span>.txt
</span><span>.hidden.txt
</span><span>ip.txt
</span><span>projects</span><span style="color:#72ab00;">/</span><span>calculator</span><span style="color:#72ab00;">/</span><span>notes.txt
</span><span>todos</span><span style="color:#72ab00;">/</span><span>books.txt
</span><span>todos</span><span style="color:#72ab00;">/</span><span>outing.txt
</span></code></pre>
</blockquote>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/wo5Szdi5VLA" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/cli-computing">Linux Command Line Computing</a> ebook.</p>
Vim tip 22: word and WORD motions2023-02-10T00:00:00+00:002023-02-10T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-22/<p>Definitions from <a href="https://vimhelp.org/motion.txt.html#word">:h word</a> and <a href="https://vimhelp.org/motion.txt.html#WORD">:h WORD</a> are quoted below to explain the difference between <strong>word</strong> and <strong>WORD</strong>.</p>
<blockquote>
<p><strong>word</strong> A word consists of a sequence of letters, digits and underscores, or a sequence of other non-blank characters, separated with white space (spaces, tabs, <code><EOL></code>). This can be changed with the <code>iskeyword</code> option. An empty line is also considered to be a word.</p>
</blockquote>
<blockquote>
<p><strong>WORD</strong> A WORD consists of a sequence of non-blank characters, separated with white space. An empty line is also considered to be a WORD.</p>
</blockquote>
<p>word based motions:</p>
<ul>
<li><kbd>w</kbd> move to the start of the next word</li>
<li><kbd>b</kbd> move to the beginning of the current word if the cursor is <em>not</em> at the start of word. Otherwise, move to the beginning of the previous word</li>
<li><kbd>e</kbd> move to the end of the current word if cursor is <em>not</em> at the end of word. Otherwise, move to the end of next word</li>
<li><kbd>ge</kbd> move to the end of the previous word</li>
<li><kbd>3w</kbd> move 3 words forward
<ul>
<li>Similarly, a number can be prefixed for all the other commands discussed here</li>
</ul>
</li>
</ul>
<p>WORD based motions:</p>
<ul>
<li><kbd>W</kbd> move to the start of the next WORD
<ul>
<li><code>192.1.168.43;hello</code> is considered as a single WORD, but has multiple words</li>
</ul>
</li>
<li><kbd>B</kbd> move to the beginning of the current WORD if the cursor is <em>not</em> at the start of WORD. Otherwise, move to the beginning of the previous WORD</li>
<li><kbd>E</kbd> move to the end of the current WORD if cursor is <em>not</em> at the end of WORD. Otherwise, move to the end of next WORD</li>
<li><kbd>gE</kbd> move to the end of the previous WORD</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> All of these motions will work across lines. For example, if the cursor is on the last word of a line, pressing <kbd>w</kbd> will move to the start of the first word in the next line.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/-1MKL82cbw8" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
Python tip 23: map, filter and reduce2023-02-07T00:00:00+00:002023-02-07T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-23/<p>Many operations on container objects can be defined in terms of these three concepts. For example, if you want to sum the square of all even numbers:</p>
<ul>
<li>separating out even numbers is <strong>Filter</strong> (i.e. only elements that satisfy a condition are retained)</li>
<li>square of such numbers is <strong>Map</strong> (i.e. each element is transformed by a mapping function)</li>
<li>final sum is <strong>Reduce</strong> (i.e. you get one value out of multiple values)</li>
</ul>
<p>One or more of these operations may be absent depending on the problem statement. Each of these steps will be first illustrated using straightforward code and then the equivalent list comprehensions (and generator expressions) are also shown.</p>
<p>The first of these steps could look like:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span style="background-color:#562d56bf;color:#f8f8f8;">def</span><span> </span><span style="color:#5597d6;">get_evens</span><span>(iterable):
</span><span style="color:#b3933a;">... </span><span>op </span><span style="color:#72ab00;">= </span><span>[]
</span><span style="color:#b3933a;">... </span><span style="color:#72ab00;">for </span><span>n </span><span style="color:#72ab00;">in </span><span>iterable:
</span><span style="color:#b3933a;">... </span><span style="color:#72ab00;">if </span><span>n </span><span style="color:#72ab00;">% </span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;">== </span><span style="color:#b3933a;">0</span><span>:
</span><span style="color:#b3933a;">... </span><span>op.</span><span style="color:#5597d6;">append</span><span>(n)
</span><span style="color:#b3933a;">... </span><span style="color:#72ab00;">return </span><span>op
</span><span style="color:#b3933a;">...
</span><span style="color:#72ab00;">>>> </span><span>nums </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#b3933a;">53</span><span>, </span><span style="color:#b3933a;">32</span><span>, </span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#b3933a;">11</span><span>, </span><span style="color:#b3933a;">5</span><span>, </span><span style="color:#b3933a;">2</span><span>]
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">get_evens</span><span>(nums)
</span><span>[</span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#b3933a;">32</span><span>, </span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#b3933a;">2</span><span>]
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>[n </span><span style="color:#72ab00;">for </span><span>n </span><span style="color:#72ab00;">in </span><span>nums </span><span style="color:#72ab00;">if </span><span>n </span><span style="color:#72ab00;">% </span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;">== </span><span style="color:#b3933a;">0</span><span>]
</span><span>[</span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#b3933a;">32</span><span>, </span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#b3933a;">2</span><span>]
</span></code></pre>
<p>The second step could be:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span style="background-color:#562d56bf;color:#f8f8f8;">def</span><span> </span><span style="color:#5597d6;">sqr_evens</span><span>(iterable):
</span><span style="color:#b3933a;">... </span><span>op </span><span style="color:#72ab00;">= </span><span>[]
</span><span style="color:#b3933a;">... </span><span style="color:#72ab00;">for </span><span>n </span><span style="color:#72ab00;">in </span><span>iterable:
</span><span style="color:#b3933a;">... </span><span style="color:#72ab00;">if </span><span>n </span><span style="color:#72ab00;">% </span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;">== </span><span style="color:#b3933a;">0</span><span>:
</span><span style="color:#b3933a;">... </span><span>op.</span><span style="color:#5597d6;">append</span><span>(n </span><span style="color:#72ab00;">* </span><span>n)
</span><span style="color:#b3933a;">... </span><span style="color:#72ab00;">return </span><span>op
</span><span style="color:#b3933a;">...
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">sqr_evens</span><span>(nums)
</span><span>[</span><span style="color:#b3933a;">10000</span><span>, </span><span style="color:#b3933a;">1024</span><span>, </span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#b3933a;">4</span><span>]
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>[n </span><span style="color:#72ab00;">* </span><span>n </span><span style="color:#72ab00;">for </span><span>n </span><span style="color:#72ab00;">in </span><span>nums </span><span style="color:#72ab00;">if </span><span>n </span><span style="color:#72ab00;">% </span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;">== </span><span style="color:#b3933a;">0</span><span>]
</span><span>[</span><span style="color:#b3933a;">10000</span><span>, </span><span style="color:#b3933a;">1024</span><span>, </span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#b3933a;">4</span><span>]
</span></code></pre>
<p>And finally, the third step could be:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span style="background-color:#562d56bf;color:#f8f8f8;">def</span><span> </span><span style="color:#5597d6;">sum_sqr_evens</span><span>(iterable):
</span><span style="color:#b3933a;">... </span><span>total </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">0
</span><span style="color:#b3933a;">... </span><span style="color:#72ab00;">for </span><span>n </span><span style="color:#72ab00;">in </span><span>iterable:
</span><span style="color:#b3933a;">... </span><span style="color:#72ab00;">if </span><span>n </span><span style="color:#72ab00;">% </span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;">== </span><span style="color:#b3933a;">0</span><span>:
</span><span style="color:#b3933a;">... </span><span>total </span><span style="color:#72ab00;">+= </span><span>n </span><span style="color:#72ab00;">* </span><span>n
</span><span style="color:#b3933a;">... </span><span style="color:#72ab00;">return </span><span>total
</span><span style="color:#b3933a;">...
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">sum_sqr_evens</span><span>(nums)
</span><span style="color:#b3933a;">11028
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">sum</span><span>(n </span><span style="color:#72ab00;">* </span><span>n </span><span style="color:#72ab00;">for </span><span>n </span><span style="color:#72ab00;">in </span><span>nums </span><span style="color:#72ab00;">if </span><span>n </span><span style="color:#72ab00;">% </span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;">== </span><span style="color:#b3933a;">0</span><span>)
</span><span style="color:#b3933a;">11028
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> You can also use <a href="https://docs.python.org/3/library/functions.html#map">map()</a>, <a href="https://docs.python.org/3/library/functions.html#filter">filter()</a> and <a href="https://docs.python.org/3/library/functools.html#functools.reduce">functools.reduce()</a> for such problems.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/jB4cazhpThA" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 21: working with tabs2023-02-01T00:00:00+00:002023-02-01T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-21/<p>Multiple files can be opened in Vim within the same tab page and/or in different tabs. From <a href="https://vimhelp.org/windows.txt.html#windows-intro">:h windows-intro</a>:</p>
<blockquote>
<ul>
<li>A buffer is the in-memory text of a file.</li>
<li>A window is a viewport on a buffer.</li>
<li>A tab page is a collection of windows.</li>
</ul>
</blockquote>
<ul>
<li><kbd>:tabe filename</kbd> open the given file in a new tab (<code>:tabe</code> is short for <code>:tabedit</code>)
<ul>
<li>if <code>filename</code> isn't specified, you'll get an unnamed empty window</li>
<li>by default, the new tab is opened to the right of the current tab</li>
<li><kbd>:0tabe</kbd> open as the first tab</li>
<li><kbd>:$tabe</kbd> open as the last tab</li>
<li>see <a href="https://vimhelp.org/tabpage.txt.html#%3Atabnew">:h :tabe</a> for more details and features</li>
</ul>
</li>
</ul>
<p>Switching between tabs:</p>
<ul>
<li><kbd>:tabn</kbd> switch to the next tab (<code>:tabn</code> is short for <code>:tabnext</code>)
<ul>
<li>if tabs to the right are exhausted, switch to the first tab</li>
<li><kbd>gt</kbd> and <kbd>Ctrl</kbd>+<kbd>Page Down</kbd> can also be used</li>
<li><kbd>2gt</kbd> switch to the second tab (the number specified is absolute, not relative)</li>
</ul>
</li>
<li><kbd>:tabp</kbd> switch to the previous tab (<code>:tabp</code> is short for <code>:tabprevious</code>)
<ul>
<li>if tabs to the left are exhausted, switch to the last tab</li>
<li><kbd>gT</kbd> and <kbd>Ctrl</kbd>+<kbd>Page Up</kbd> can also be used</li>
</ul>
</li>
<li><kbd>:tabr</kbd> switch to the first tab (<code>:tabr</code> is short for <code>:tabrewind</code>)
<ul>
<li><kbd>:tabfirst</kbd> can also be used</li>
</ul>
</li>
<li><kbd>:tabl</kbd> switch to the last tab (<code>:tabl</code> is short for <code>:tablast</code>)</li>
</ul>
<p>Moving tabs:</p>
<ul>
<li><kbd>:tabm N</kbd> move the current tab to after <code>N</code> tabs from the start (<code>:tabm</code> is short for <code>:tabmove</code>)
<ul>
<li><kbd>:tabm 0</kbd> move the current tab to the beginning</li>
<li><kbd>:tabm</kbd> move the current tab to the end</li>
</ul>
</li>
<li><kbd>:tabm +N</kbd> move the current tab <code>N</code> positions to the right</li>
<li><kbd>:tabm -N</kbd> move the current tab <code>N</code> positions to the left</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> Buffer list includes all the files opened in all the tabs. You can also use the mouse to switch/move tabs in GVim.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/pQpWrrrYVTI" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 22: grep options to suppress stdout and stderr2023-01-25T00:00:00+00:002023-01-25T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-22/<p>While writing scripts, sometimes you just need to know if a file contains the pattern and act based on the exit status of the command. Instead of redirecting the output to <code>/dev/null</code> you can use the <code>-q</code> option. This will avoid printing anything on <code>stdout</code> and also provides speed benefit as processing would be stopped as soon as the given condition is satisfied.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat find.txt
</span><span style="color:#5597d6;">The</span><span> find command is more versatile than recursive options </span><span style="color:#72ab00;">and
</span><span style="color:#72ab00;">and </span><span style="color:#b39f04;">extended</span><span> globs. </span><span style="color:#5597d6;">Apart</span><span> from searching based on filename, it
</span><span>has provisions to </span><span style="color:#b39f04;">match</span><span> based on the the file characteristics
</span><span>like size </span><span style="color:#72ab00;">and</span><span> time.
</span><span>
</span><span>$ grep </span><span style="color:#72ab00;">-</span><span>wE </span><span style="color:#d07711;">'(\w+) \1'</span><span> find.txt
</span><span>has provisions to </span><span style="color:#b39f04;">match</span><span> based on the the file characteristics
</span><span>$ grep </span><span style="color:#72ab00;">-</span><span>qwE </span><span style="color:#d07711;">'(\w+) \1'</span><span> find.txt
</span><span>$ echo </span><span style="color:#5597d6;">$?
</span><span style="color:#b3933a;">0
</span><span>
</span><span>$ grep </span><span style="color:#72ab00;">-</span><span>q </span><span style="color:#d07711;">'xyz'</span><span> find.txt
</span><span>$ echo </span><span style="color:#5597d6;">$?
</span><span style="color:#b3933a;">1
</span><span>
</span><span>$ grep </span><span style="color:#72ab00;">-</span><span>qwE </span><span style="color:#d07711;">'(\w+) \1'</span><span> find.txt </span><span style="color:#72ab00;">&&</span><span> echo </span><span style="color:#d07711;">'Repeated words found!'
</span><span style="color:#5597d6;">Repeated</span><span> words found!
</span></code></pre>
<p>The <code>-s</code> option will suppress the error messages that are intended for the <code>stderr</code> stream.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># when the input file doesn't exist
</span><span>$ grep </span><span style="color:#d07711;">'in'</span><span> xyz.txt
</span><span style="color:#b3933a;">grep:</span><span> xyz.txt</span><span style="color:#72ab00;">: </span><span style="color:#5597d6;">No</span><span> such file </span><span style="color:#72ab00;">or</span><span> directory
</span><span>$ grep </span><span style="color:#72ab00;">-</span><span>s </span><span style="color:#d07711;">'in'</span><span> xyz.txt
</span><span>$ echo </span><span style="color:#5597d6;">$?
</span><span style="color:#b3933a;">2
</span><span>
</span><span style="color:#7f8989;"># when sufficient permission is not available
</span><span>$ touch </span><span style="color:#72ab00;">new</span><span>.txt
</span><span>$ chmod </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#72ab00;">new</span><span>.txt
</span><span>$ grep </span><span style="color:#d07711;">'rose' </span><span style="color:#72ab00;">new</span><span>.txt
</span><span style="color:#b3933a;">grep: </span><span style="color:#72ab00;">new</span><span>.txt</span><span style="color:#72ab00;">: </span><span style="color:#5597d6;">Permission</span><span> denied
</span><span>$ grep </span><span style="color:#72ab00;">-</span><span>s </span><span style="color:#d07711;">'rose' </span><span style="color:#72ab00;">new</span><span>.txt
</span><span>$ echo </span><span style="color:#5597d6;">$?
</span><span style="color:#b3933a;">2
</span></code></pre>
<p>Errors regarding regular expressions and invalid options will be on the <code>stderr</code> stream even when the <code>-s</code> option is used.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ grep </span><span style="color:#72ab00;">-</span><span>sE </span><span style="color:#d07711;">'a('</span><span> find.txt
</span><span style="color:#b3933a;">grep: </span><span style="color:#5597d6;">Unmatched </span><span>( </span><span style="color:#72ab00;">or</span><span> \(
</span><span>
</span><span>$ grep </span><span style="color:#72ab00;">-</span><span>sE </span><span style="color:#d07711;">'a('</span><span> find.txt </span><span style="color:#b3933a;">2</span><span style="color:#72ab00;">> </span><span style="color:#c49a39;">/dev/</span><span style="color:#72ab00;">nu</span><span>ll
</span><span>$ echo </span><span style="color:#5597d6;">$?
</span><span style="color:#b3933a;">2
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> Check out my <a href="https://github.com/learnbyexample/command_help/blob/master/ch"><code>ch</code> command line tool</a> for a practical example of using the <code>-q</code> option.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/Pjud7hEjZ6Q" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep">CLI text processing with GNU grep and ripgrep</a> ebook if you are interested in learning about <code>GNU grep</code> and <code>ripgrep</code> commands in more detail.</p>
Python Regex Surprises2023-01-21T00:00:00+00:002023-02-04T00:00:00+00:00https://learnbyexample.github.io/python-regex-surprises/<p>In this post, you'll find a few regular expression examples that might surprise you. Some are Python specific and some are applicable to other regex flavors as well. To make it more interesting, these are framed as questions for you to ponder upon. Answers are hidden by default.</p>
<p align="center"><img src="/images/python_regex_surprises.png" alt="Python Regex Surprises" /></p>
<p align="center"><i>Poster created using <a href="https://www.canva.com/">Canva</a></i></p>
<p><img src="/images/info.svg" alt="info" /> If you are not familiar with regular expressions, check out my <a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> ebook.</p>
<span id="continue-reading"></span><br>
<h2 id="vs-z">$ vs \Z<a class="zola-anchor" href="#vs-z" aria-label="Anchor link for: vs-z">🔗</a></h2>
<p>Are the <code>$</code> and <code>\Z</code> anchors equivalent?</p>
<details><summary><i style="color:gray">Click to view answer</i></summary>
<p><code>$</code> can match both the end of string and just before <code>\n</code> if it is the last character. <code>\Z</code> will only match the end of string.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>greeting </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'hi there</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">have a nice day</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">day</span><span style="color:#72ab00;">$</span><span style="color:#d07711;">'</span><span>, greeting))
</span><span style="color:#b3933a;">True
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">day</span><span style="color:#aeb52b;">\n</span><span style="color:#72ab00;">$</span><span style="color:#d07711;">'</span><span>, greeting))
</span><span style="color:#b3933a;">True
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">day</span><span style="color:#72ab00;">\Z</span><span style="color:#d07711;">'</span><span>, greeting))
</span><span style="color:#b3933a;">False
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">day</span><span style="color:#aeb52b;">\n</span><span style="color:#72ab00;">\Z</span><span style="color:#d07711;">'</span><span>, greeting))
</span><span style="color:#b3933a;">True
</span></code></pre>
</details>
<br>
<h2 id="slicing-vs-start-and-end-arguments">Slicing vs start and end arguments<a class="zola-anchor" href="#slicing-vs-start-and-end-arguments" aria-label="Anchor link for: slicing-vs-start-and-end-arguments">🔗</a></h2>
<p>Did you know that you can specify <em>start</em> and <em>end</em> index arguments for compiled methods?</p>
<blockquote>
<p><code>Pattern.search(string[, pos[, endpos]])</code></p>
</blockquote>
<p>Now, here's a conundrum:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>word_pat </span><span style="color:#72ab00;">= </span><span>re.</span><span style="color:#5597d6;">compile</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\A</span><span style="color:#7c8f4c;">at</span><span style="color:#d07711;">'</span><span>)
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(word_pat.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#d07711;">'cater'</span><span>[</span><span style="color:#b3933a;">1</span><span>:]))
</span><span style="color:#b3933a;">True
</span><span>
</span><span style="color:#7f8989;"># what will be the output?
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(word_pat.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#d07711;">'cater'</span><span>, </span><span style="color:#b3933a;">1</span><span>))
</span></code></pre>
<details><summary><i style="color:gray">Click to view answer</i></summary>
Specifying a greater than <code>0</code> start index when using <code>\A</code> is always going to return <code>False</code>. This is because, as far as the <code>search()</code> method is concerned, only the search space has been narrowed — the anchor positions haven't changed. When slicing is used, you are creating an entirely new string object with new anchor positions.
</details>
<br>
<h2 id="do-and-match-after-the-last-newline">Do ^ and $ match after the last newline?<a class="zola-anchor" href="#do-and-match-after-the-last-newline" aria-label="Anchor link for: do-and-match-after-the-last-newline">🔗</a></h2>
<p>When you use the <code>re.MULTILINE</code> flag, the <code>^</code> and <code>$</code> anchors will match at the start and end of every input line. Question is, will they also match after a newline character at the end of the input?</p>
<details><summary><i style="color:gray">Click to view answer</i></summary>
<p>Yes, they will both match after the last newline character.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">print</span><span>(re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">(?m)^</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'apple '</span><span>, </span><span style="color:#d07711;">'1</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">2</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">'</span><span>))
</span><span>apple </span><span style="color:#b3933a;">1
</span><span>apple </span><span style="color:#b3933a;">2
</span><span>apple
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">print</span><span>(re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">(?m)$</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">' banana'</span><span>, </span><span style="color:#d07711;">'1</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">2</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">'</span><span>))
</span><span style="color:#b3933a;">1 </span><span>banana
</span><span style="color:#b3933a;">2 </span><span>banana
</span><span> banana
</span></code></pre>
</details>
<br>
<h2 id="word-boundary-vs-lookarounds">Word boundary vs lookarounds<a class="zola-anchor" href="#word-boundary-vs-lookarounds" aria-label="Anchor link for: word-boundary-vs-lookarounds">🔗</a></h2>
<p><code>\b..\b</code> is same as <code>(?<!\w)..(?!\w)</code> — True or False?</p>
<details><summary><i style="color:gray">Click to view answer</i></summary>
<p>False! <code>\b</code> matches both the start and end of word locations. In the below example, <code>\b..\b</code> doesn't necessarily mean that the first <code>\b</code> will match only the start of word location and the second <code>\b</code> will match only the end of word location. They can be any combination! For example, <code>I</code> followed by space in the input string here is using the start of word location for both the conditions. Similarly, space followed by <code>2</code> is using the end of word location for both the conditions.</p>
<p>In contrast, the negative lookarounds version ensures that there are no word characters around any two characters. Also, such assertions will always be satisfied at the start of string and the end of string respectively. But <code>\b</code> depends on the presence of word characters. For example, <code>!</code> at the end of the input string here matches the lookaround assertion but not word boundary.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>ip </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'I have 12, he has 2!'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\b</span><span style="color:#aeb52b;">..</span><span style="color:#72ab00;">\b</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'{</span><span style="text-decoration:underline;font-style:italic;color:#d2a8a1;">\g</span><span style="color:#d07711;"><0>}'</span><span>, ip)
</span><span style="color:#d07711;">'{I }have </span><span style="color:#aeb52b;">{12}</span><span style="color:#d07711;">{, }</span><span style="color:#aeb52b;">{he}</span><span style="color:#d07711;"> has{ 2}!'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?<!\w</span><span style="color:#7c8f4c;">)</span><span style="color:#aeb52b;">..</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?!\w</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'{</span><span style="text-decoration:underline;font-style:italic;color:#d2a8a1;">\g</span><span style="color:#d07711;"><0>}'</span><span>, ip)
</span><span style="color:#d07711;">'I have </span><span style="color:#aeb52b;">{12}</span><span style="color:#d07711;">, </span><span style="color:#aeb52b;">{he}</span><span style="color:#d07711;"> has {2!}'
</span></code></pre>
</details>
<br>
<h2 id="undefined-escape-sequences">Undefined escape sequences<a class="zola-anchor" href="#undefined-escape-sequences" aria-label="Anchor link for: undefined-escape-sequences">🔗</a></h2>
<p>If you use undefined escape sequences like <code>\e</code>, will you get an error or will it match the unescaped character (<code>e</code> for this example`)?</p>
<details><summary><i style="color:gray">Click to view answer</i></summary>
<p>Python raises an exception for escape sequences that are not defined. Apart from sequences defined for character sets (for example <code>\d</code>, <code>\w</code>, <code>\s</code>, etc), these are allowed: <code>\a \b \f \n \N \r \t \u \U \v \x \\</code> where <code>\b</code> means backspace only in character classes. Also, <code>\u</code> and <code>\U</code> are valid only in Unicode patterns.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\t</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'cat</span><span style="color:#aeb52b;">\t</span><span style="color:#d07711;">dog'</span><span>))
</span><span style="color:#b3933a;">True
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\c</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'cat</span><span style="color:#aeb52b;">\t</span><span style="color:#d07711;">dog'</span><span>))
</span><span>re.error: bad escape \</span><span style="background-color:#562d56bf;color:#f8f8f8;">c at position 0</span><span>
</span></code></pre>
</details>
<br>
<h2 id="using-octal-and-hexadecimal-escapes-in-the-replacement-section">Using octal and hexadecimal escapes in the replacement section<a class="zola-anchor" href="#using-octal-and-hexadecimal-escapes-in-the-replacement-section" aria-label="Anchor link for: using-octal-and-hexadecimal-escapes-in-the-replacement-section">🔗</a></h2>
<p>In string literals, you can use octal, hexadecimal and unicode escapes to represent a character. For example, <code>'\174'</code> is same as using <code>'|'</code>. Do you know which of these escapes you can use inside raw strings in the replacement section of the <code>sub()</code> function?</p>
<details><summary><i style="color:gray">Click to view answer</i></summary>
<p>Only octal escapes are allowed inside raw strings in the replacement section. If you are otherwise not using the <code>\</code> character, then using normal strings in the replacement section is preferred as it will also allow hexadecimal and unicode escapes.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">,</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\x</span><span style="color:#7c8f4c;">7c</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'1,2'</span><span>)
</span><span>re.error: bad escape \</span><span style="background-color:#562d56bf;color:#f8f8f8;">x at position 0</span><span>
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">,</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\17</span><span style="color:#7c8f4c;">4</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'1,2'</span><span>)
</span><span style="color:#d07711;">'1|2'
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">,</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\x7c</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'1,2'</span><span>)
</span><span style="color:#d07711;">'1|2'
</span></code></pre>
<p>I feel like it would have been rather better if octal escapes were also not allowed. That would have allowed us to use <code>\0</code> instead of <code>\g<0></code> for backreferencing the entire matched portion in the replacement section.</p>
</details>
<br>
<h2 id="using-escape-sequences-for-metacharacters">Using escape sequences for metacharacters<a class="zola-anchor" href="#using-escape-sequences-for-metacharacters" aria-label="Anchor link for: using-escape-sequences-for-metacharacters">🔗</a></h2>
<p>In the search section, if you use an escape (for example, <code>\x7c</code> to represent the <code>|</code> character), will it behave as the alternation metacharacter or match it literally?</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">2</span><span style="color:#72ab00;">|</span><span style="color:#7c8f4c;">3</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'5'</span><span>, </span><span style="color:#d07711;">'12|30'</span><span>)
</span><span style="color:#d07711;">'15|50'
</span><span>
</span><span style="color:#7f8989;"># what will be the output?
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">2</span><span style="color:#aeb52b;">\x</span><span style="color:#7c8f4c;">7c3</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'5'</span><span>, </span><span style="color:#d07711;">'12|30'</span><span>)
</span></code></pre>
<details><summary><i style="color:gray">Click to view answer</i></summary>
<p>The output will be <code>'150'</code> since escapes will be treated literally.</p>
</details>
<br>
<h2 id="empty-matches">Empty matches<a class="zola-anchor" href="#empty-matches" aria-label="Anchor link for: empty-matches">🔗</a></h2>
<p>You are likely to have come across this before:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># what will be the output?
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">,]</span><span style="color:#72ab00;">*</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">{</span><span style="color:#aeb52b;">\g</span><span style="color:#7c8f4c;"><0>}</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">',cat,tiger'</span><span>)
</span></code></pre>
<details><summary><i style="color:gray">Click to view answer</i></summary>
<p>See also <a href="https://www.regular-expressions.info/zerolength.html">Zero-Length Matches</a>.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># there is an extra empty string match at the end of matches
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">,]</span><span style="color:#72ab00;">*</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">{</span><span style="color:#aeb52b;">\g</span><span style="color:#7c8f4c;"><0>}</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">',cat,tiger'</span><span>)
</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">{}</span><span style="color:#d07711;">,</span><span style="color:#aeb52b;">{cat}{}</span><span style="color:#d07711;">,</span><span style="color:#aeb52b;">{tiger}{}</span><span style="color:#d07711;">'
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">,]</span><span style="color:#72ab00;">*+</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">{</span><span style="color:#aeb52b;">\g</span><span style="color:#7c8f4c;"><0>}</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">',cat,tiger'</span><span>)
</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">{}</span><span style="color:#d07711;">,</span><span style="color:#aeb52b;">{cat}{}</span><span style="color:#d07711;">,</span><span style="color:#aeb52b;">{tiger}{}</span><span style="color:#d07711;">'
</span><span>
</span><span style="color:#7f8989;"># use lookarounds as a workaround
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">?<![</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">,]</span><span style="color:#7c8f4c;">)</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">,]</span><span style="color:#72ab00;">*</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">{</span><span style="color:#aeb52b;">\g</span><span style="color:#7c8f4c;"><0>}</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">',cat,tiger'</span><span>)
</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">{}</span><span style="color:#d07711;">,</span><span style="color:#aeb52b;">{cat}</span><span style="color:#d07711;">,</span><span style="color:#aeb52b;">{tiger}</span><span style="color:#d07711;">'
</span></code></pre>
</details>
<br>
<h2 id="can-quantifiers-be-grouped-out">Can quantifiers be grouped out?<a class="zola-anchor" href="#can-quantifiers-be-grouped-out" aria-label="Anchor link for: can-quantifiers-be-grouped-out">🔗</a></h2>
<p>Similar to <code>a(b+c)d = abd+acd</code> in maths, you get <code>a(b|c)d = abd|acd</code> in regular expressions. <code>(a*|b*)</code> is same as <code>(a|b)*</code> — True or False?</p>
<details><summary><i style="color:gray">Click to view answer</i></summary>
<p align="center"><img src="/images/mini/regexp_gotcha_1.png" alt="Regexp grouping with quantifiers gotcha" /></p>
<p align="center">Railroad diagram created using <a href="https://www.debuggex.com/">debuggex.com</a></p>
<p>False. Because <code>(a*|b*)</code> will match only sequences like <code>a</code>, <code>aaa</code>, <code>bb</code>, <code>bbbbbbbb</code>. But <code>(a|b)*</code> can match mixed sequences like <code>ababbba</code> too.</p>
</details>
<br>
<h2 id="portion-captured-by-a-quantified-group">Portion captured by a quantified group<a class="zola-anchor" href="#portion-captured-by-a-quantified-group" aria-label="Anchor link for: portion-captured-by-a-quantified-group">🔗</a></h2>
<p>This should be another familiar regex gotcha:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># what will be the output?
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\A</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">,]</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">,)</span><span style="color:#72ab00;">{3}</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">,]</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\1</span><span style="color:#7c8f4c;">(</span><span style="color:#72ab00;">\2</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'1,2,3,4,5,6,7'</span><span>)
</span></code></pre>
<details><summary><i style="color:gray">Click to view answer</i></summary>
<p>Referring to the text matched by a capture group with a quantifier will give only the last match, not the entire match. You'll need an outer capture group to get the entire matched portion.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\A</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">,]</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">,)</span><span style="color:#72ab00;">{3}</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">,]</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\1</span><span style="color:#7c8f4c;">(</span><span style="color:#72ab00;">\2</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'1,2,3,4,5,6,7'</span><span>)
</span><span style="color:#d07711;">'3,(4),5,6,7'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\A</span><span style="color:#7c8f4c;">((?:</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">,]</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">,)</span><span style="color:#72ab00;">{3}</span><span style="color:#7c8f4c;">)(</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">,]</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\1</span><span style="color:#7c8f4c;">(</span><span style="color:#72ab00;">\2</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'1,2,3,4,5,6,7'</span><span>)
</span><span style="color:#d07711;">'1,2,3,(4),5,6,7'
</span></code></pre>
</details>
<br>
<h2 id="character-combinations">Character combinations<a class="zola-anchor" href="#character-combinations" aria-label="Anchor link for: character-combinations">🔗</a></h2>
<p><code>\b[a-z](on|no)[a-z]\b</code> is same as <code>\b[a-z][on]{2}[a-z]\b</code> — True or False?</p>
<details><summary><i style="color:gray">Click to view answer</i></summary>
<p>False. <code>[on]{2}</code> will also match <code>oo</code> and <code>nn</code>.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>words </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'known mood know pony inns'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\b</span><span style="color:#aeb52b;">[</span><span style="color:#b3933a;">a-z</span><span style="color:#aeb52b;">]</span><span style="color:#7c8f4c;">(?:on</span><span style="color:#72ab00;">|</span><span style="color:#7c8f4c;">no)</span><span style="color:#aeb52b;">[</span><span style="color:#b3933a;">a-z</span><span style="color:#aeb52b;">]</span><span style="color:#72ab00;">\b</span><span style="color:#d07711;">'</span><span>, words)
</span><span>[</span><span style="color:#d07711;">'know'</span><span>, </span><span style="color:#d07711;">'pony'</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\b</span><span style="color:#aeb52b;">[</span><span style="color:#b3933a;">a-z</span><span style="color:#aeb52b;">][on]</span><span style="color:#72ab00;">{2}</span><span style="color:#aeb52b;">[</span><span style="color:#b3933a;">a-z</span><span style="color:#aeb52b;">]</span><span style="color:#72ab00;">\b</span><span style="color:#d07711;">'</span><span>, words)
</span><span>[</span><span style="color:#d07711;">'mood'</span><span>, </span><span style="color:#d07711;">'know'</span><span>, </span><span style="color:#d07711;">'pony'</span><span>, </span><span style="color:#d07711;">'inns'</span><span>]
</span></code></pre>
</details>
<br>
<h2 id="greedy-vs-possessive">Greedy vs Possessive<a class="zola-anchor" href="#greedy-vs-possessive" aria-label="Anchor link for: greedy-vs-possessive">🔗</a></h2>
<p>Suppose you want to match integer numbers greater than or equal to <code>100</code> where these numbers can optionally have leading zeros. Will the below code work? If not, what would you use instead?</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>numbers </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'42 314 001 12 00984'
</span><span>
</span><span style="color:#7f8989;"># will this work?
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">0</span><span style="color:#72ab00;">*</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">{3,}</span><span style="color:#d07711;">'</span><span>, numbers)
</span></code></pre>
<details><summary><i style="color:gray">Click to view answer</i></summary>
<p>No. You can either modify the pattern such that <code>0*</code> won't interfere or use possessive quantifiers to prevent backtracking.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>numbers </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'42 314 001 12 00984'
</span><span>
</span><span style="color:#7f8989;"># this solution fails because 0* and \d{3,} can both match leading zeros
</span><span style="color:#7f8989;"># and greedy quantifiers will give up characters to help overall RE succeed
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">0</span><span style="color:#72ab00;">*</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">{3,}</span><span style="color:#d07711;">'</span><span>, numbers)
</span><span>[</span><span style="color:#d07711;">'314'</span><span>, </span><span style="color:#d07711;">'001'</span><span>, </span><span style="color:#d07711;">'00984'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># 0*+ is possessive, will never give back leading zeros
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">0</span><span style="color:#72ab00;">*+</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">{3,}</span><span style="color:#d07711;">'</span><span>, numbers)
</span><span>[</span><span style="color:#d07711;">'314'</span><span>, </span><span style="color:#d07711;">'00984'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># workaround if possessive isn't supported
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">0</span><span style="color:#72ab00;">*</span><span style="color:#aeb52b;">[</span><span style="color:#b3933a;">1-9</span><span style="color:#aeb52b;">]\d</span><span style="color:#72ab00;">{2,}</span><span style="color:#d07711;">'</span><span>, numbers)
</span><span>[</span><span style="color:#d07711;">'314'</span><span>, </span><span style="color:#d07711;">'00984'</span><span>]
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See my blog post on <a href="https://learnbyexample.github.io/python-regex-possessive-quantifier/">possessive quantifiers and atomic grouping</a> for more examples, details about catastrophic backtracking and so on.</p>
</details>
<br>
<h2 id="optional-flags-argument">Optional flags argument<a class="zola-anchor" href="#optional-flags-argument" aria-label="Anchor link for: optional-flags-argument">🔗</a></h2>
<p>Will the <code>sub()</code> function in the code sample below match case insensitively or not?</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">key</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'KEY portkey oKey Keyed'</span><span>, re.I)
</span><span>[</span><span style="color:#d07711;">'KEY'</span><span>, </span><span style="color:#d07711;">'key'</span><span>, </span><span style="color:#d07711;">'Key'</span><span>, </span><span style="color:#d07711;">'Key'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># what will be the output?
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">key</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">\g</span><span style="color:#7c8f4c;"><0>)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'KEY portkey oKey Keyed'</span><span>, re.I)
</span></code></pre>
<details><summary><i style="color:gray">Click to view answer</i></summary>
<p>You should always pass flags as a keyword argument. Using it as positional argument leads to a common mistake between <code>re.findall()</code> and <code>re.sub()</code> functions due to difference in their placement.</p>
<blockquote>
<p><code>re.findall(pattern, string, flags=0)</code></p>
<p><code>re.sub(pattern, repl, string, count=0, flags=0)</code></p>
</blockquote>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> +</span><span>re.I
</span><span style="color:#b3933a;">2
</span><span>
</span><span style="color:#7f8989;"># works because flags is the only optional argument for findall
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">key</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'KEY portkey oKey Keyed'</span><span>, re.I)
</span><span>[</span><span style="color:#d07711;">'KEY'</span><span>, </span><span style="color:#d07711;">'key'</span><span>, </span><span style="color:#d07711;">'Key'</span><span>, </span><span style="color:#d07711;">'Key'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># wrong usage, but no error because re.I has a value of 2
</span><span style="color:#7f8989;"># so, this is same as specifying count=2
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">key</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">\g</span><span style="color:#7c8f4c;"><0>)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'KEY portkey oKey Keyed'</span><span>, re.I)
</span><span style="color:#d07711;">'KEY port(key) oKey Keyed'
</span><span>
</span><span style="color:#7f8989;"># correct use of keyword argument
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">key</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">\g</span><span style="color:#7c8f4c;"><0>)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'KEY portkey oKey Keyed'</span><span>, </span><span style="color:#5597d6;">flags</span><span style="color:#72ab00;">=</span><span>re.I)
</span><span style="color:#d07711;">'(KEY) port(key) o(Key) (Key)ed'
</span><span style="color:#7f8989;"># alternatively, you can use inline flags to avoid this problem altogether
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">(?i)</span><span style="color:#7c8f4c;">key</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">\g</span><span style="color:#7c8f4c;"><0>)</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'KEY portkey oKey Keyed'</span><span>)
</span><span style="color:#d07711;">'(KEY) port(key) o(Key) (Key)ed'
</span></code></pre>
</details>
<br>
<h2 id="re-vs-regex-module-flags">re vs regex module flags<a class="zola-anchor" href="#re-vs-regex-module-flags" aria-label="Anchor link for: re-vs-regex-module-flags">🔗</a></h2>
<p>The third-party <code>regex</code> module is handy for advanced features like subexpression calls, skipping matches and so on. Can you use <code>re</code> module flag constants with the <code>regex</code> module?</p>
<details><summary><i style="color:gray">Click to view answer</i></summary>
<p>When using the flags argument with the <code>regex</code> module, the constants should also be used from the <code>regex</code> module.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> +</span><span>re.A
</span><span style="color:#b3933a;">256
</span><span>
</span><span style="color:#72ab00;">>>> +</span><span>regex.A
</span><span style="color:#b3933a;">128
</span></code></pre>
<p>Again, you can use inline flags to avoid such issues.</p>
</details>
<br>
<h2 id="understanding-python-re-gex-book">Understanding Python re(gex)? book<a class="zola-anchor" href="#understanding-python-re-gex-book" aria-label="Anchor link for: understanding-python-re-gex-book">🔗</a></h2>
<p>Visit my GitHub repo <a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> for details about the book I wrote on Python regular expressions. The ebook uses plenty of examples to explain the concepts from the very beginning and step by step introduces more advanced concepts. The book also covers the <a href="https://pypi.org/project/regex/">third-party module regex</a>.</p>
Vim tip 20: character based motions within the current line2023-01-10T00:00:00+00:002023-01-10T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-20/<p>These commands allow you to move based on a single character search, <strong>within the current line only</strong>.</p>
<ul>
<li><kbd>f(</kbd> move forward to the next occurrence of character <code>(</code></li>
<li><kbd>fb</kbd> move forward to the next occurrence of character <code>b</code></li>
<li><kbd>3f"</kbd> move forward to the third occurrence of character <code>"</code></li>
<li><kbd>t;</kbd> move forward to the character just before <code>;</code></li>
<li><kbd>3tx</kbd> move forward to the character just before the third occurrence of character <code>x</code></li>
<li><kbd>Fa</kbd> move backward to the character <code>a</code></li>
<li><kbd>Ta</kbd> move backward to the character just after <code>a</code></li>
<li><kbd>;</kbd> repeat previous <code>f</code> or <code>F</code> or <code>t</code> or <code>T</code> motion in the same direction</li>
<li><kbd>,</kbd> repeat previous <code>f</code> or <code>F</code> or <code>t</code> or <code>T</code> motion in the opposite direction
<ul>
<li>for example, <kbd>tc</kbd> becomes <kbd>Tc</kbd> and vice versa</li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> Note that the previously used count prefix wouldn't be repeated with <kbd>;</kbd> or <kbd>,</kbd> commands, but you can use a new count prefix. If you pressed a wrong motion command, use the <kbd>Esc</kbd> key to abandon the search instead of continuing with the wrongly chosen command.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/WFRJmeaQr60" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 21: inplace file editing with GNU awk2023-01-04T00:00:00+00:002023-09-02T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-21/<p>You can use the <code>-i</code> option with <code>GNU awk</code> to load libraries. The <code>inplace</code> library comes by default with the <code>GNU awk</code> installation. Thus, you can use <code>-i inplace</code> to modify the original input itself. Make sure to test that the code is working as intended before using this option.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat table.txt
</span><span>brown bread mat cake </span><span style="color:#b3933a;">42
</span><span>blue cake mug shirt </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">7
</span><span>yellow banana window shoes </span><span style="color:#b3933a;">3.14
</span><span>
</span><span style="color:#7f8989;"># retain only the first and third fields
</span><span>$ awk </span><span style="color:#72ab00;">-</span><span>i inplace </span><span style="color:#d07711;">'{print $1, $3}'</span><span> table.txt
</span><span>$ cat table.txt
</span><span>brown mat
</span><span>blue mug
</span><span>yellow window
</span></code></pre>
<p>You can provide a backup extension by setting the <code>inplace::suffix</code> special variable. For example, if the input file is <code>ip.txt</code> and <code>inplace::suffix='.orig'</code> is used, the backup file will be named as <code>ip.txt.orig</code>.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat marks.txt
</span><span> </span><span style="color:#5597d6;">Name Physics Maths
</span><span> </span><span style="color:#5597d6;">Moe </span><span style="color:#b3933a;">76 82
</span><span style="color:#5597d6;">Raj </span><span style="color:#b3933a;">56 64
</span><span>
</span><span>$ awk </span><span style="color:#72ab00;">-</span><span>i inplace </span><span style="color:#72ab00;">-</span><span>v inplace::suffix=</span><span style="color:#d07711;">'.bkp' </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">OFS</span><span style="color:#72ab00;">=</span><span>, </span><span style="color:#d07711;">'{$1=$1} 1'</span><span> marks.txt
</span><span>$ cat marks.txt
</span><span style="color:#5597d6;">Name</span><span>,</span><span style="color:#5597d6;">Physics</span><span>,</span><span style="color:#5597d6;">Maths
</span><span style="color:#5597d6;">Moe</span><span>,</span><span style="color:#b3933a;">76</span><span>,</span><span style="color:#b3933a;">82
</span><span style="color:#5597d6;">Raj</span><span>,</span><span style="color:#b3933a;">56</span><span>,</span><span style="color:#b3933a;">64
</span><span>
</span><span style="color:#7f8989;"># original file is preserved in 'marks.txt.bkp'
</span><span>$ cat marks.txt.bkp
</span><span> </span><span style="color:#5597d6;">Name Physics Maths
</span><span> </span><span style="color:#5597d6;">Moe </span><span style="color:#b3933a;">76 82
</span><span style="color:#5597d6;">Raj </span><span style="color:#b3933a;">56 64
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> Earlier versions of <code>GNU awk</code> used <code>INPLACE_SUFFIX</code> variable instead of <code>inplace::suffix</code>. Also, you can use <code>inplace::enable</code> variable to dynamically control whether files should be inplaced or not. See <a href="https://www.gnu.org/software/gawk/manual/gawk.html#Extension-Sample-Inplace">gawk manual: Enabling In-Place File Editing</a> for more details.</p>
<p><img src="/images/warning.svg" alt="warning" /> See <a href="https://unix.stackexchange.com/q/749645/109046">this unix.stackexchange thread</a> for details about security implications of using the <code>-i</code> option and workarounds.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/yfO-HVTBoSI" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/learn_gnuawk">CLI text processing with GNU awk</a> ebook if you are interested in learning about the <code>GNU awk</code> command in more detail.</p>
2022: year in perspective2022-12-30T00:00:00+00:002022-12-30T00:00:00+00:00https://learnbyexample.github.io/2022-year-in-perspective/<p><strong>TL;DR</strong>: Published two programming ebooks, wrote several blog posts, recorded plenty of Youtube videos, newsletter prospered, improved Twitter audience, read 100+ novels, and so on. Had an excellent year in terms of ebook sales 😇</p>
<span id="continue-reading"></span><br>
<h2 id="books-published">Books published<a class="zola-anchor" href="#books-published" aria-label="Anchor link for: books-published">🔗</a></h2>
<ul>
<li><a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> — concise learning resource for beginner to intermediate level Vim users, published in March</li>
<li><a href="https://github.com/learnbyexample/cli-computing">Computing from the Command Line</a> — Linux command line tools and Shell Scripting for beginner to intermediate level users, published in November</li>
</ul>
<p align="center"><img src="/images/books/2022_books.png" alt="Programming books published in 2022" /></p>
<br>
<h2 id="workshops">Workshops<a class="zola-anchor" href="#workshops" aria-label="Anchor link for: workshops">🔗</a></h2>
<p>Offline workshops were back on menu this year. I got only one offer though. Surprisingly, it was for Python basics, despite students already having had a course in their first year. It was a nice experience for me, thanks to the enthusiasm shown by the students.</p>
<p>And it was good to see <a href="https://barcampbangalore.com/bcb/">BarCamp Bangalore</a> being organized again. Gave a talk about my ebook publishing experience (I had also written a <a href="https://learnbyexample.github.io/my-book-writing-experience/">blog post</a> on this topic last year).</p>
<br>
<h2 id="blog-posts">Blog posts<a class="zola-anchor" href="#blog-posts" aria-label="Anchor link for: blog-posts">🔗</a></h2>
<p>Here are my favorite posts I wrote this year:</p>
<ul>
<li><a href="https://learnbyexample.github.io/interactive-linux-cli-exercises/">Interactive Linux CLI Text Processing Exercises</a></li>
<li><a href="https://learnbyexample.github.io/textual-first-impressions/">Building TUIs with textual: first impressions</a></li>
<li><a href="https://learnbyexample.github.io/python-regex-possessive-quantifier/">Python 3.11: possessive quantifiers added to re module</a>
<ul>
<li>came to know that this post <a href="https://twitter.com/s_gruppetta_ct/status/1596097402381176832">ranks high during online searches</a></li>
</ul>
</li>
<li><a href="https://learnbyexample.github.io/duplicates-irrespective-field-order/">Removing duplicates irrespective of field order</a></li>
</ul>
<p>I continued posting <a href="https://learnbyexample.github.io/tips/">weekly programming tips</a> (Python, Linux, Vim) that are short and easy to digest and wrote some <a href="https://learnbyexample.github.io/mini/">mini blog posts</a> as well.</p>
<br>
<h2 id="tools">Tools<a class="zola-anchor" href="#tools" aria-label="Anchor link for: tools">🔗</a></h2>
<p>During the last two months of the year, I learned a bit of <a href="https://textual.textualize.io/">Textual</a> and wrote a couple of TUI apps:</p>
<ul>
<li><a href="https://github.com/learnbyexample/TUI-apps/tree/main/SquareTicTacToe">Square Tic Tac Toe</a> — form a square with 4 corners</li>
<li><a href="https://github.com/learnbyexample/TUI-apps/tree/main/CLI-Exercises">Linux CLI Text Processing Exercises</a> — test your CLI text processing skills</li>
</ul>
<p>I found the framework much easier to use compared to my experience with Tkinter.</p>
<br>
<h2 id="youtube">Youtube<a class="zola-anchor" href="#youtube" aria-label="Anchor link for: youtube">🔗</a></h2>
<p>While working on the Vim Reference Guide, I felt that some of the commands really needed video demos for easier understanding. So, I gave myself another chance at recording videos. I kept them simple and short, and with consistent practice I did better than my attempts a few years back. I then extended my new found enthusiasm to programming tips, ebook promo videos, etc. Visit my <a href="https://www.youtube.com/c/learnbyexample42">youtube channel</a> for interesting tech nuggets.</p>
<p>Here are some of the tools I use:</p>
<ul>
<li><a href="https://github.com/MaartenBaert/ssr">SimpleScreenRecorder</a> — recording video, really simple to use</li>
<li><a href="https://github.com/WyattBlue/auto-editor">auto-editor</a> — removing silent portions from video recordings</li>
<li><a href="https://github.com/FFmpeg/FFmpeg">FFmpeg</a> — video processing, padding for example (<code>FFmpeg</code> is also a major part of the <code>auto-editor</code> solution)</li>
<li><a href="https://www.canva.com/">Canva</a> — video thumbnails (I also use this app for ebook covers)</li>
</ul>
<br>
<h2 id="book-sales">Book sales<a class="zola-anchor" href="#book-sales" aria-label="Anchor link for: book-sales">🔗</a></h2>
<p>Revenue from ebook sales were almost 50% higher than last year!! As I wrote in the <a href="https://learnbyexample.github.io/wild-ride-2021/">2021 was a wild ride</a> post, I've been paying more attention to marketing and seems like my efforts are paying off. Here's my Gumroad revenue chart for 2022:</p>
<p align="center"><img src="/images/books/gumroad_sales_2022.png" alt="Gumroad sales in 2022" /></p>
<p>The peaks were during the two ebook releases. Additionally, I had less than half the above revenue from Leanpub. Last year, sales from Leanpub and Gumroad were nearly the same.</p>
<br>
<h2 id="newsletter">Newsletter<a class="zola-anchor" href="#newsletter" aria-label="Anchor link for: newsletter">🔗</a></h2>
<p>Last November, I started <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> newsletter. I've managed to send an email every Friday without fail so far and I'm proud of that. Sometimes I had to schedule issues a week ahead. Currently about 600 subscribers and some readers are even paying me monthly despite being a free newsletter.</p>
<br>
<h2 id="building-twitter-audience">Building Twitter audience<a class="zola-anchor" href="#building-twitter-audience" aria-label="Anchor link for: building-twitter-audience">🔗</a></h2>
<p>As part of marketing efforts last year, I started building my Twitter audience as well. Follower count was less than 400 in July and about 1100 in December last year. Now it has crossed 2900. I'm not focused on increasing follower count with plethora of engagement inducing tweets. Just trying to be consistent and promoting all sorts of interesting links I come across. That said, I'd like to try creating cool infographics (probably using Canva) next year.</p>
<p><a href="https://twitter.com/learn_byexample">Follow me on Twitter</a> for interesting tech nuggets 😉</p>
<br>
<h2 id="fictional-reading">Fictional reading<a class="zola-anchor" href="#fictional-reading" aria-label="Anchor link for: fictional-reading">🔗</a></h2>
<p>I enjoy reading fantasy and science-fiction novels. I read 100+ SFF books this year and recently wrote a post listing <a href="https://learnbyexample.github.io/escapist-reviews/lists/2022-favorite-sff-novels/">my favorites</a>.</p>
<p>I also got a chance to beta read <a href="https://www.goodreads.com/book/show/60872852-tongue-eater">Tongue Eater</a>, <a href="https://www.goodreads.com/book/show/61324650-soul-relic">Soul Relic</a>, <a href="https://www.goodreads.com/book/show/60969058-the-umbral-storm">The Umbral Storm</a> and <a href="https://www.goodreads.com/book/show/62586822-the-book-of-zog">The Book of Zog</a>. I find these a good way to give back to the writing community, having myself received plenty of support from strangers.</p>
<br>
<h2 id="goals-for-2023">Goals for 2023<a class="zola-anchor" href="#goals-for-2023" aria-label="Anchor link for: goals-for-2023">🔗</a></h2>
<p>I met most of my goals this year, so that's a nice feeling. Contributing to open source projects needs a lot more focus in the coming year. I'm not likely to publish a <em>new</em> ebook in 2023. Instead, I'm planning to update my existing books and that will probably take more than a year. Apart from catching up to new features and improving existing examples/exercises, I'll also focus on changing book titles and cover images. And, I'll likely create interactive apps for exercises.</p>
<p>I need to also find something other than books to keep me creatively busy. It has been more than 4 years since I first published an ebook and 6 years since I started writing programming tutorials.</p>
<br>
<p>Here's wishing you a very happy, healthy and prosperous 2023 👍 😇</p>
Python tip 21: sorting iterables based on a key2022-12-28T00:00:00+00:002022-12-28T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-21/<p>You can use the <code>sort()</code> method for sorting lists inplace. The <code>sorted()</code> function can be used to get a sorted list from any iterable.</p>
<p>The <code>key</code> argument accepts the name of a function (i.e. function object) for custom sorting. If two elements are deemed equal based on the result of the function, the original order will be maintained (<em>stable sorting</em>). Here are some examples:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># based on the absolute value of an element
</span><span style="color:#7f8989;"># note that the input order is maintained for all three values of "4"
</span><span style="color:#72ab00;">>>> </span><span>nums </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">4</span><span>, </span><span style="color:#b3933a;">309</span><span>, </span><span style="color:#b3933a;">4.0</span><span>, </span><span style="color:#b3933a;">34</span><span>, </span><span style="color:#b3933a;">0.2</span><span>, </span><span style="color:#b3933a;">4</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>nums.</span><span style="color:#5597d6;">sort</span><span>(</span><span style="color:#5597d6;">key</span><span style="color:#72ab00;">=</span><span style="color:#b39f04;">abs</span><span>)
</span><span style="color:#72ab00;">>>> </span><span>nums
</span><span>[</span><span style="color:#b3933a;">0.2</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">4</span><span>, </span><span style="color:#b3933a;">4.0</span><span>, </span><span style="color:#b3933a;">4</span><span>, </span><span style="color:#b3933a;">34</span><span>, </span><span style="color:#b3933a;">309</span><span>]
</span><span>
</span><span style="color:#7f8989;"># based on the length of an element
</span><span style="color:#72ab00;">>>> </span><span>words </span><span style="color:#72ab00;">= </span><span>(</span><span style="color:#d07711;">'morello'</span><span>, </span><span style="color:#d07711;">'irk'</span><span>, </span><span style="color:#d07711;">'fuliginous'</span><span>, </span><span style="color:#d07711;">'crusado'</span><span>, </span><span style="color:#d07711;">'seam'</span><span>)
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">sorted</span><span>(words, </span><span style="color:#5597d6;">key</span><span style="color:#72ab00;">=</span><span style="color:#b39f04;">len</span><span>, </span><span style="color:#5597d6;">reverse</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">True</span><span>)
</span><span>[</span><span style="color:#d07711;">'fuliginous'</span><span>, </span><span style="color:#d07711;">'morello'</span><span>, </span><span style="color:#d07711;">'crusado'</span><span>, </span><span style="color:#d07711;">'seam'</span><span>, </span><span style="color:#d07711;">'irk'</span><span>]
</span></code></pre>
<p>Here are some examples using <a href="https://docs.python.org/3/tutorial/controlflow.html#lambda-expressions">lambda expressions</a>:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># sorting dictionaries based on values
</span><span style="color:#72ab00;">>>> </span><span>vehicles </span><span style="color:#72ab00;">= </span><span>{</span><span style="color:#d07711;">'bus'</span><span>: </span><span style="color:#b3933a;">10</span><span>, </span><span style="color:#d07711;">'car'</span><span>: </span><span style="color:#b3933a;">20</span><span>, </span><span style="color:#d07711;">'jeep'</span><span>: </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#d07711;">'cycle'</span><span>: </span><span style="color:#b3933a;">5</span><span>}
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">sorted</span><span>(vehicles, </span><span style="color:#5597d6;">key</span><span style="color:#72ab00;">=lambda </span><span style="color:#5597d6;">k</span><span>: vehicles[k])
</span><span>[</span><span style="color:#d07711;">'jeep'</span><span>, </span><span style="color:#d07711;">'cycle'</span><span>, </span><span style="color:#d07711;">'bus'</span><span>, </span><span style="color:#d07711;">'car'</span><span>]
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">dict</span><span>(</span><span style="color:#b39f04;">sorted</span><span>(vehicles.</span><span style="color:#5597d6;">items</span><span>(), </span><span style="color:#5597d6;">key</span><span style="color:#72ab00;">=lambda </span><span style="color:#5597d6;">t</span><span>: t[</span><span style="color:#b3933a;">1</span><span>]))
</span><span>{</span><span style="color:#d07711;">'jeep'</span><span>: </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#d07711;">'cycle'</span><span>: </span><span style="color:#b3933a;">5</span><span>, </span><span style="color:#d07711;">'bus'</span><span>: </span><span style="color:#b3933a;">10</span><span>, </span><span style="color:#d07711;">'car'</span><span>: </span><span style="color:#b3933a;">20</span><span>}
</span><span>
</span><span style="color:#7f8989;"># based on file extension
</span><span style="color:#72ab00;">>>> </span><span>files </span><span style="color:#72ab00;">= </span><span>(</span><span style="color:#d07711;">'report.txt'</span><span>, </span><span style="color:#d07711;">'hello.py'</span><span>, </span><span style="color:#d07711;">'calc.sh'</span><span>, </span><span style="color:#d07711;">'tictactoe.py'</span><span>)
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">sorted</span><span>(files, </span><span style="color:#5597d6;">key</span><span style="color:#72ab00;">=lambda </span><span style="color:#5597d6;">f</span><span>: f.</span><span style="color:#5597d6;">rsplit</span><span>(</span><span style="color:#d07711;">'.'</span><span>, </span><span style="color:#b3933a;">1</span><span>)[</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">1</span><span>])
</span><span>[</span><span style="color:#d07711;">'hello.py'</span><span>, </span><span style="color:#d07711;">'tictactoe.py'</span><span>, </span><span style="color:#d07711;">'calc.sh'</span><span>, </span><span style="color:#d07711;">'report.txt'</span><span>]
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://docs.python.org/3/howto/sorting.html">docs.python HOWTOs: Sorting</a>.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/ERWykO67GTU" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 19: working with buffers2022-12-20T00:00:00+00:002022-12-20T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-19/<p>Multiple files can be opened in Vim within the same tab page and/or in different tabs. From <a href="https://vimhelp.org/windows.txt.html#windows-intro">:h windows-intro</a>:</p>
<blockquote>
<ul>
<li>A buffer is the in-memory text of a file.</li>
<li>A window is a viewport on a buffer.</li>
<li>A tab page is a collection of windows.</li>
</ul>
</blockquote>
<ul>
<li><kbd>:e</kbd> refreshes the current buffer (<code>:e</code> is short for <code>:edit</code>)</li>
<li><kbd>:e filename</kbd> open a particular file by its path, in the same window</li>
<li><kbd>:e #</kbd> switch back to the previous buffer, won't work if that buffer is not named</li>
<li><kbd>Ctrl</kbd>+<kbd>6</kbd> switch back to the previous buffer, works even if that buffer is not named
<ul>
<li><kbd>Ctrl</kbd>+<kbd>^</kbd> can also be used</li>
</ul>
</li>
<li><kbd>:e #1</kbd> open the first buffer, and so on</li>
<li><kbd>:buffers</kbd> show all buffers
<ul>
<li><kbd>:ls</kbd> or <kbd>:files</kbd> can also be used</li>
</ul>
</li>
<li><kbd>:bn</kbd> open the next file in the buffer list (<code>:bn</code> is short for <code>:bnext</code>)
<ul>
<li>opens the first buffer if you are on the last buffer</li>
</ul>
</li>
<li><kbd>:bp</kbd> open the previous file in the buffer list (<code>:bp</code> is short for <code>:bprevious</code>)
<ul>
<li>opens the last buffer if you are on the first buffer</li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> Use <kbd>:set hidden</kbd> if you want to switch to another buffer even if there are unsaved changes in the current buffer. Instead of this setting, you can also use <kbd>:hide edit filename</kbd> to hide the current unsaved buffer. You'll still get an error if you try to quit Vim without saving such buffers, unless you use the <code>!</code> modifier.</p>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/options.txt.html#%27autowrite%27">:h 'autowrite'</a> option if you want to automatically save changes when moving to another buffer.</p>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/usr_22.txt.html#22.4">:h 22.4</a> and <a href="https://vimhelp.org/windows.txt.html#buffer-hidden">:h buffer-hidden</a> for user and reference manuals on working with buffer list.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/dJO16IwfSko" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 20: expand and unexpand2022-12-14T00:00:00+00:002022-12-14T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-20/<p>These two commands will help you convert tabs to spaces and vice versa. Both these commands support options to customize the width of tab stops and which occurrences should be converted.</p>
<p>The default expansion aligns at multiples of <code>8</code> columns (calculated in terms of bytes).</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># 'apple' = 5 bytes, \t converts to 3 spaces
</span><span style="color:#7f8989;"># 'banana' = 6 bytes, \t converts to 2 spaces
</span><span style="color:#7f8989;"># 'a' and 'b' = 1 byte, \t converts to 7 spaces
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'apple\tbanana\tcherry\na\tb\tc\n' </span><span style="color:#72ab00;">|</span><span> expand
</span><span>apple banana cherry
</span><span>a b c
</span><span>
</span><span style="color:#7f8989;"># 'αλε' = 6 bytes, \t converts to 2 spaces
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'αλε\tπού\n' </span><span style="color:#72ab00;">|</span><span> expand
</span><span>αλε πού
</span></code></pre>
<p>By default, the <code>unexpand</code> command converts initial blank (space or tab) characters to tabs. The first occurrence of a non-blank character will stop the conversion. By default, every <code>8</code> columns worth of blanks is converted to a tab.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># input is 8 spaces followed by 'a' and then more characters
</span><span style="color:#7f8989;"># the initial 8 spaces is converted to a tab character
</span><span style="color:#7f8989;"># 'a' stops any further conversion, since it is a non-blank character
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">' a b c\n' </span><span style="color:#72ab00;">|</span><span> unexpand </span><span style="color:#72ab00;">|</span><span> cat </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">T
</span><span style="color:#72ab00;">^</span><span style="color:#5597d6;">Ia</span><span> b c
</span><span>
</span><span style="color:#7f8989;"># input is 9 spaces followed by 'a' and then more characters
</span><span style="color:#7f8989;"># the initial 8 spaces is converted to a tab character
</span><span style="color:#7f8989;"># remaining space is left as is
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">' a b c\n' </span><span style="color:#72ab00;">|</span><span> unexpand </span><span style="color:#72ab00;">|</span><span> cat </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">T
</span><span style="color:#72ab00;">^</span><span style="color:#5597d6;">I</span><span> a b c
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/tsJCtE6oZDs" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://learnbyexample.github.io/cli_text_processing_coreutils/expand-unexpand.html">expand and unexpand</a> chapter my <a href="https://github.com/learnbyexample/cli_text_processing_coreutils">Command line text processing with GNU Coreutils</a> ebook for more examples, options, etc.</p>
Interactive Linux CLI Text Processing Exercises2022-12-09T00:00:00+00:002025-01-09T00:00:00+00:00https://learnbyexample.github.io/interactive-linux-cli-exercises/<p>Having an interactive program that automatically loads questions and checks the solution is wonderful to have while learning a topic. This <a href="https://github.com/learnbyexample/TUI-apps/tree/main/CLI-Exercises">TUI app</a> has 60+ beginner to intermediate level exercises for Linux CLI text processing tools.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/CLI-Exercises/cli_exercises.png" alt="Sample screenshot for CLI exercises" loading="lazy" /></p>
<span id="continue-reading"></span><br>
<h2 id="installation">Installation<a class="zola-anchor" href="#installation" aria-label="Anchor link for: installation">🔗</a></h2>
<p>Last month, I started learning a Python TUI framework called <a href="https://textual.textualize.io/">Textual</a>. After working on a <a href="https://github.com/learnbyexample/TUI-apps/tree/main/SquareTicTacToe">4x4 board game</a>, I made an interactive app to help you test your CLI text processing skills with 60+ beginner to intermediate level exercises.</p>
<p>You'll need Python for this. This app is available on PyPI as <a href="https://pypi.org/project/cliexercises/">cliexercises</a>. Example installation instructions are shown below, adjust them based on your preferences and OS.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># virtual environment
</span><span style="color:#5597d6;">$</span><span> python3</span><span style="color:#5597d6;"> -m</span><span> venv textual_apps
</span><span style="color:#5597d6;">$</span><span> cd textual_apps
</span><span style="color:#5597d6;">$</span><span> source bin/activate
</span><span style="color:#5597d6;">$</span><span> pip install cliexercises
</span><span>
</span><span style="color:#7f8989;"># launch the app
</span><span style="color:#5597d6;">$</span><span> cliexercises
</span></code></pre>
<p>To run the app without having to enter the virtual environment again, add this alias to <code>.bashrc</code> (or equivalent):</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># you'll have to change the path
</span><span style="color:#b39f04;">alias </span><span style="color:#c23f31;">cliexercises</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'/path/to/textual_apps/bin/cliexercises'
</span></code></pre>
<p>As an alternative to manually managing such virtual environments, you can use <a href="https://github.com/pypa/pipx">https://github.com/pypa/pipx</a> instead:</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> pipx install cliexercises
</span><span style="color:#5597d6;">$</span><span> cliexercises
</span></code></pre>
<p>As yet another alternative, you can install <code>textual==0.85.2</code> (see <a href="https://textual.textualize.io/getting_started/">Textual documentation</a> for more details), clone my <a href="https://github.com/learnbyexample/TUI-apps">TUI-apps repository</a> and run the <code>cli_exercises.py</code> file.</p>
<p>Adjust the terminal dimensions for the widgets to appear properly, for example 84x25 (characters x lines).</p>
<br>
<h2 id="video-demo">Video demo<a class="zola-anchor" href="#video-demo" aria-label="Anchor link for: video-demo">🔗</a></h2>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/lcm_F7zPzRY" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<h2 id="brief-guide">Brief Guide<a class="zola-anchor" href="#brief-guide" aria-label="Anchor link for: brief-guide">🔗</a></h2>
<ul>
<li>Press <strong>Ctrl+p</strong> and <strong>Ctrl+n</strong> to navigate the questions list.</li>
<li>Type the command in the box below the question.</li>
<li>Press <strong>Enter</strong> to execute the command.
<ul>
<li>Output would be displayed below the command box.</li>
<li>If the output matches the expected results, the command box will turn <em>green</em> and a reference solution will also be shown.</li>
<li>Issues due to errors and timeout (about <code>2</code> seconds) will be displayed in <em>red</em>.</li>
</ul>
</li>
<li>Press <strong>Ctrl+s</strong> to toggle the reference solution box.</li>
<li>Press <strong>Ctrl+t</strong> to toggle between light and dark themes.</li>
<li>Press <strong>Ctrl+q</strong> to quit the app.</li>
<li>Some basic readline-like shortcuts are supported, for example <strong>Ctrl+u</strong>, <strong>Ctrl+k</strong>, <strong>Ctrl+w</strong>, etc</li>
</ul>
<p>Your progress is automatically saved and restored. Already answered questions will be skipped.</p>
<blockquote>
<p><img src="/images/warning.svg" alt="warning" /> There is no safeguard against the command you are executing. They are treated as if you typed them from a shell session.</p>
</blockquote>
<p>For more detailed instructions, visit <a href="https://github.com/learnbyexample/TUI-apps/tree/main/CLI-Exercises">https://github.com/learnbyexample/TUI-apps/tree/main/CLI-Exercises</a></p>
<br>
<h2 id="ebook">Ebook<a class="zola-anchor" href="#ebook" aria-label="Anchor link for: ebook">🔗</a></h2>
<p>The exercises in this app have been adapted from my <a href="https://learnbyexample.github.io/books/">Command Line</a> ebooks.</p>
<br>
<h2 id="feedback">Feedback<a class="zola-anchor" href="#feedback" aria-label="Anchor link for: feedback">🔗</a></h2>
<p>I'd highly appreciate your feedback. Please file <a href="https://github.com/learnbyexample/TUI-apps/issues">an issue</a> if there are bugs, crashes, etc.</p>
<p>Hope you find this TUI app useful. Happy learning :)</p>
Python tip 20: saving and loading json2022-12-07T00:00:00+00:002022-12-07T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-20/<p>JSON (JavaScript Object Notation) is one of the ways you can store and retrieve data necessary for functioning of an application. For example, my projects <a href="https://github.com/learnbyexample/py_regular_expressions/tree/master/interactive_exercises">Python regex exercises</a> and <a href="https://github.com/learnbyexample/TUI-apps/blob/main/CLI-Exercises">Linux CLI text processing exercises</a> need to load questions and save user progress. You might wonder why not just a plain text file? I needed <code>dict</code> in the code anyway and JSON offered seamless transition. Also, this arrangement avoided having to write extra code and test it for potential parsing issues.</p>
<p>The <code>json</code> builtin module is handy for such purposes. Here's an example of saving a <code>dict</code> object:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> import </span><span>json
</span><span style="color:#72ab00;">>>> </span><span>marks </span><span style="color:#72ab00;">= </span><span>{</span><span style="color:#d07711;">'Rahul'</span><span>: </span><span style="color:#b3933a;">86</span><span>, </span><span style="color:#d07711;">'Ravi'</span><span>: </span><span style="color:#b3933a;">92</span><span>, </span><span style="color:#d07711;">'Rohit'</span><span>: </span><span style="color:#b3933a;">75</span><span>, </span><span style="color:#d07711;">'Rajan'</span><span>: </span><span style="color:#b3933a;">79</span><span>}
</span><span style="color:#72ab00;">>>> with </span><span style="color:#b39f04;">open</span><span>(</span><span style="color:#d07711;">'marks.json'</span><span>, </span><span style="color:#d07711;">'w'</span><span>) </span><span style="color:#72ab00;">as </span><span>f:
</span><span style="color:#b3933a;">... </span><span>json.</span><span style="color:#5597d6;">dump</span><span>(marks, f, </span><span style="color:#5597d6;">indent</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">4</span><span>)
</span><span style="color:#b3933a;">...
</span></code></pre>
<p>In the above example, <code>indent</code> is used for pretty printing. Here's how the file looks like:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span>$ cat marks.json
</span><span>{
</span><span> </span><span style="color:#d07711;">"Rahul"</span><span>: </span><span style="color:#b3933a;">86</span><span>,
</span><span> </span><span style="color:#d07711;">"Ravi"</span><span>: </span><span style="color:#b3933a;">92</span><span>,
</span><span> </span><span style="color:#d07711;">"Rohit"</span><span>: </span><span style="color:#b3933a;">75</span><span>,
</span><span> </span><span style="color:#d07711;">"Rajan"</span><span>: </span><span style="color:#b3933a;">79
</span><span>}
</span></code></pre>
<p>And here's an example of loading a JSON file:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> with </span><span style="color:#b39f04;">open</span><span>(</span><span style="color:#d07711;">'marks.json'</span><span>) </span><span style="color:#72ab00;">as </span><span>f:
</span><span style="color:#b3933a;">... </span><span>marks </span><span style="color:#72ab00;">= </span><span>json.</span><span style="color:#5597d6;">load</span><span>(f)
</span><span style="color:#b3933a;">...
</span><span style="color:#72ab00;">>>> </span><span>marks
</span><span>{</span><span style="color:#d07711;">'Rahul'</span><span>: </span><span style="color:#b3933a;">86</span><span>, </span><span style="color:#d07711;">'Ravi'</span><span>: </span><span style="color:#b3933a;">92</span><span>, </span><span style="color:#d07711;">'Rohit'</span><span>: </span><span style="color:#b3933a;">75</span><span>, </span><span style="color:#d07711;">'Rajan'</span><span>: </span><span style="color:#b3933a;">79</span><span>}
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://docs.python.org/3/library/json.html">docs.python: json</a> for documentation, more examples, other methods, caveats and so on.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/Fd28UTqcU3k" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 18: moving within long lines2022-11-29T00:00:00+00:002022-11-29T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-18/<p>Here are Normal mode commands you can use to move within long lines that are spread over multiple screen lines:</p>
<ul>
<li><kbd>g0</kbd> move to the beginning of the current screen line</li>
<li><kbd>g^</kbd> move to the first non-blank character of the current screen line</li>
<li><kbd>g$</kbd> move to the end of the current screen line</li>
<li><kbd>gj</kbd> move down by one screen line, prefix a count to move down by that many screen lines</li>
<li><kbd>gk</kbd> move up by one screen line, prefix a count to move up by that many screen lines</li>
<li><kbd>gm</kbd> move to the middle of the current screen line
<ul>
<li><strong>Note</strong> that this is based on the screen width, not the number of characters in the line!</li>
</ul>
</li>
<li><kbd>gM</kbd> move to the middle of the current line
<ul>
<li><strong>Note</strong> that this is based on the total number of characters in the line</li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/motion.txt.html#left-right-motions">:h left-right-motions</a> for more details.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/VfN_LJIiyaI" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 19: extended globs2022-11-23T00:00:00+00:002022-11-23T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-19/<p>The Bash shell provides <code>extglob</code> option for advanced pattern matching of filenames. These will help you apply regexp like quantifiers, provide alternate patterns and negation. From <code>man bash</code>:</p>
<table><thead><tr><th>Extended glob</th><th>Description</th></tr></thead><tbody>
<tr><td><code>?(pattern-list)</code></td><td>Matches zero or one occurrence of the given patterns</td></tr>
<tr><td><code>*(pattern-list)</code></td><td>Matches zero or more occurrences of the given patterns</td></tr>
<tr><td><code>+(pattern-list)</code></td><td>Matches one or more occurrences of the given patterns</td></tr>
<tr><td><code>@(pattern-list)</code></td><td>Matches one of the given patterns</td></tr>
<tr><td><code>!(pattern-list)</code></td><td>Matches anything except one of the given patterns</td></tr>
</tbody></table>
<p>Extended globs are disabled by default. You can use <code>shopt -s extglob</code> and <code>shopt -u extglob</code> to set and unset this option respectively.</p>
<p>Here are some examples (visit <a href="https://github.com/learnbyexample/cli-computing/raw/master/example_files/scripts/globs.sh">globs.sh</a> to get the script used below).</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ source globs.sh
</span><span>$ ls
</span><span style="color:#b3933a;">100</span><span>.sh f1.txt f4.txt hi.sh math.h report</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">02</span><span>.log
</span><span style="color:#b3933a;">42</span><span>.txt f2_old.txt f7.txt ip.txt notes.txt report</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">04</span><span>.log
</span><span>calc.py f2.txt hello.py main.c report</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">00</span><span>.log report</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">98</span><span>.log
</span><span>
</span><span style="color:#7f8989;"># one or more digits followed by '.' and then zero or more characters
</span><span>$ ls </span><span style="color:#72ab00;">+</span><span>([</span><span style="color:#b3933a;">0</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">9</span><span>]).</span><span style="color:#72ab00;">*
</span><span style="color:#b3933a;">100</span><span>.sh </span><span style="color:#b3933a;">42</span><span>.txt
</span><span>
</span><span style="color:#7f8989;"># same as: ls *.c *.sh
</span><span>$ ls </span><span style="color:#72ab00;">*</span><span>.@(c</span><span style="color:#72ab00;">|</span><span>sh)
</span><span style="color:#b3933a;">100</span><span>.sh hi.sh main.c
</span><span>
</span><span style="color:#7f8989;"># not ending with '.txt'
</span><span>$ ls </span><span style="color:#72ab00;">!</span><span>(</span><span style="color:#72ab00;">*</span><span>.txt)
</span><span style="color:#b3933a;">100</span><span>.sh hello.py main.c report</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">00</span><span>.log report</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">04</span><span>.log
</span><span>calc.py hi.sh math.h report</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">02</span><span>.log report</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">98</span><span>.log
</span><span>
</span><span style="color:#7f8989;"># not ending with '.txt' or '.log'
</span><span>$ ls </span><span style="color:#72ab00;">*</span><span>.</span><span style="color:#72ab00;">!</span><span>(txt</span><span style="color:#72ab00;">|</span><span>log)
</span><span style="color:#b3933a;">100</span><span>.sh calc.py hello.py hi.sh main.c math.h
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/9DF0PBWfiX0" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/cli-computing">Linux Command Line Computing</a> ebook.</p>
Festive deals for books on Python, Linux, JavaScript, Regular Expressions and more2022-11-22T00:00:00+00:002022-11-25T00:00:00+00:00https://learnbyexample.github.io/programming-deals-2022/<p>Hello!</p>
<p>Here are some exciting deals for my programming ebooks as well as from other creators.</p>
<span id="continue-reading"></span><br>
<h2 id="my-ebooks">My ebooks<a class="zola-anchor" href="#my-ebooks" aria-label="Anchor link for: my-ebooks">🔗</a></h2>
<p>Offers valid till 30-Nov-2022:</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer">All 13 Books Bundle</a> — $10 (normal price $28)</li>
<li><a href="https://learnbyexample.gumroad.com/l/py_projects/FestiveOffer">Practice Python Projects</a> — FREE (normal price $10)</li>
<li><a href="https://learnbyexample.gumroad.com/l/js_regexp/FestiveOffer">JavaScript RegExp</a> — FREE (normal price $10)</li>
<li><a href="https://learnbyexample.gumroad.com/l/python-bundle/FestiveOffer">Learn by example Python bundle</a> — $3 (normal price $15)</li>
</ul>
<p align="center"><a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer"><img src="/images/books/all_books_bundle.png" alt="All books bundle" loading="lazy" /></a></p>
<br>
<h2 id="indie-creators">Indie creators<a class="zola-anchor" href="#indie-creators" aria-label="Anchor link for: indie-creators">🔗</a></h2>
<ul>
<li><a href="https://www.blog.pythonlibrary.org/2022/11/22/python-black-friday-cyber-monday-sales-2022/">Python books by Michael Driscoll</a> — $10 off for all books, 20% off for Teach Me Python</li>
<li><a href="https://www.pythonmorsels.com/pricing/">Python Morsels</a> — save up to $108 a year on Python Morsels, until Nov 28 (skill-honing system that helps developers deepen their Python skills)
<ul>
<li>see also author's <a href="https://treyhunner.com/2022/11/python-black-friday-and-cyber-monday-sales-2022/">blog post</a> for comprehensive links to other Python deals</li>
</ul>
</li>
<li><a href="https://adamchainz.gumroad.com/l/byddx">Boost Your Django DX</a> and <a href="https://adamchainz.gumroad.com/l/suydt">Speed Up Your Django Tests</a> — 50% off (plus further 50% off based on GDP) until Nov 28
<ul>
<li>see also author's <a href="https://adamj.eu/tech/2022/11/21/django-black-friday-deals-2022/">blog post</a> for comprehensive links to other Django-related deals</li>
</ul>
</li>
<li><a href="https://twitter.com/reuvenmlerner/status/1595402066213601280">Python, Git, and Pandas courses</a> — 40% off</li>
<li><a href="https://bhavaniravi.gumroad.com/l/technical-blogging/WRITELIKEPRO">Practical Guide to Technical Blogging</a> is 34% off and <a href="https://bhavaniravi.gumroad.com/l/LaFSj/BLACKPYTHON">Python To Projects bootcamp</a> is 20% off</li>
<li><a href="https://shrutibalasa.gumroad.com/l/css-flex-and-grid/BlackFriday22">Complete Guide to CSS Flex and Grid</a> — 60% off on all versions of the eBook (4 days starting from Nov 24)</li>
<li><a href="https://twitter.com/OzolinsJanis/status/1595743978531348480">Explain Ideas Visually</a> — 50% OFF</li>
</ul>
<br>
<h2 id="miscellaneous">Miscellaneous<a class="zola-anchor" href="#miscellaneous" aria-label="Anchor link for: miscellaneous">🔗</a></h2>
<ul>
<li><a href="https://nostarch.com/blog/2022-holiday-gift-guide">NoStarch Press</a> — Holiday Gift Guide, 35% off until Nov 28</li>
<li><a href="https://media.pragprog.com/newsletters/2022-11-18.html">The Pragmatic Bookshelf</a> — 40% off on all ebooks and audio books</li>
<li><a href="https://deals.manning.com/thanksgiving/">Manning Publications</a> — save 50% when you buy 2 or more eBooks, liveProjects, or liveVideos</li>
<li><a href="https://mailchi.mp/leanpub/monthly-sale-2022-november-black-friday">Leanpub Monthly Sale</a> — offers for programming books, bundles and courses</li>
<li><a href="https://realpython.com/giveaway/black-friday/">Real Python Giveaway</a> — a chance to win one of three prizes, until Nov 25</li>
<li><a href="https://github.com/0x90n/InfoSec-Black-Friday">InfoSec Hack Friday</a> — InfoSec related software/tools</li>
<li><a href="https://opsdisk.gumroad.com/l/cphlab/blackfriday2022">The Cyber Plumber's Lab Guide and Interactive Access</a> — 50% OFF</li>
<li><a href="https://github.com/trungdq88/Awesome-Black-Friday-Cyber-Monday">Huge list of awesome deals</a> — tools, productivity, books, courses, etc</li>
</ul>
<br>
<p>Happy learning :)</p>
Python tip 19: manipulating string case2022-11-16T00:00:00+00:002022-11-16T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-19/<p>Here are five string methods you can use for changing the case of characters. Word level transformation is determined by consecutive occurrences of alphabets, not limited to separation by whitespace characters.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>sentence </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'thIs iS a saMple StrIng'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>sentence.</span><span style="color:#5597d6;">capitalize</span><span>()
</span><span style="color:#d07711;">'This is a sample string'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>sentence.</span><span style="color:#5597d6;">title</span><span>()
</span><span style="color:#d07711;">'This Is A Sample String'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>sentence.</span><span style="color:#5597d6;">lower</span><span>()
</span><span style="color:#d07711;">'this is a sample string'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>sentence.</span><span style="color:#5597d6;">upper</span><span>()
</span><span style="color:#d07711;">'THIS IS A SAMPLE STRING'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>sentence.</span><span style="color:#5597d6;">swapcase</span><span>()
</span><span style="color:#d07711;">'THiS Is A SAmPLE sTRiNG'
</span></code></pre>
<p>The <code>string.capwords()</code> method is similar to <code>title()</code> but also allows a specific separator (default is whitespace).</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> import </span><span>string
</span><span style="color:#72ab00;">>>> </span><span>phrase </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'this-IS-a:colon:separated,PHRASE'
</span><span>
</span><span style="color:#7f8989;"># every word is transformed
</span><span style="color:#72ab00;">>>> </span><span>phrase.</span><span style="color:#5597d6;">title</span><span>()
</span><span style="color:#d07711;">'This-Is-A:Colon:Separated,Phrase'
</span><span>
</span><span style="color:#7f8989;"># colon character is used as the text boundary
</span><span style="color:#72ab00;">>>> </span><span>string.</span><span style="color:#5597d6;">capwords</span><span>(phrase, </span><span style="color:#d07711;">':'</span><span>)
</span><span style="color:#d07711;">'This-is-a:Colon:Separated,phrase'
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/HnwjKY6nK3Y" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Building TUIs with textual: first impressions2022-11-15T00:00:00+00:002022-12-09T00:00:00+00:00https://learnbyexample.github.io/textual-first-impressions/<p>Last week, I finally started exploring <a href="https://textual.textualize.io/">textual</a>. The main motivation was to start implementing a few project ideas I've had in my todo list for years. I don't particularly have a preference between TUI (terminal user interface) and GUI (graphical user interface) for these projects. Seeing a few Textual demos on twitter (courtesy <a href="https://twitter.com/willmcgugan">Will McGugan</a>) over the past few months, I felt like exploring this framework first.</p>
<p>For my first app, I picked a 4x4 board game — like Tic Tac Toe but form a square instead of a line. I came up with this variation in high school and been fond of coding it since college days.</p>
<span id="continue-reading"></span><br>
<h2 id="installation-and-tutorials">Installation and Tutorials<a class="zola-anchor" href="#installation-and-tutorials" aria-label="Anchor link for: installation-and-tutorials">🔗</a></h2>
<p>The <a href="https://textual.textualize.io/getting_started/">Getting started</a> page of the documentation will give you all the relevant installation instructions. I used <code>pip install 'textual[dev]'</code> since the development mode has nice features like <em>live editing</em>. As I looked up the <a href="https://textual.textualize.io/guide/devtools/">Devtools</a> page to link here in this blog post, I found that there's a <code>console</code> command for <code>print()</code> based debugging! That would've been handy while I was working on the game — sigh, I should've been more proactive in exploring the documentation site.</p>
<p>In the <em>Getting started</em> page, you'll also be informed about <code>python -m textual</code> (builtin demo) and <a href="https://github.com/Textualize/textual/tree/main/examples">other examples</a> in the GitHub repo.</p>
<p>After playing with the demo a bit, I went through the <a href="https://textual.textualize.io/tutorial/">tutorial</a> — shows how to build a Stopwatch app step-by-step.</p>
<p>The documentation also includes <a href="https://textual.textualize.io/guide/">Guide</a>, <a href="https://textual.textualize.io/reference/">Reference</a>, <a href="https://textual.textualize.io/api/">API</a>, etc. I gave them a cursory glance and decided to start building my game.</p>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> I should note that while I got introduced to programming in school about 20 years ago, I don't have much experience with projects that need more than a few hundred lines. I'm good with command-line tools and text processing with scripting languages like Python. I had a horrible experience writing an Android app a few years back, mainly due to object-oriented programming and the complexity of the project. I've improved a bit since then, but still feel like a newbie when it comes to working with classes.</p>
</blockquote>
<br>
<h2 id="building-square-tic-tac-toe-board-game">Building Square Tic Tac Toe board game<a class="zola-anchor" href="#building-square-tic-tac-toe-board-game" aria-label="Anchor link for: building-square-tic-tac-toe-board-game">🔗</a></h2>
<p>Similar to the step-by-step Textual tutorial, I built the game by adding features incrementally. I <a href="https://twitter.com/learn_byexample/status/1590357173519155200">tweeted my progress</a> along with screenshots and recordings. Here's a summary:</p>
<ul>
<li>Managed to place 16 buttons in a grid layout</li>
<li>Buttons now respond to clicking! And in response, the computer plays a random move</li>
<li>Recording below shows 3 games: User wins, AI wins, Tie</li>
<li>Added Easy/Hard modes — it is impossible to beat the AI in hard mode</li>
<li>Almost done! Layout is better now and starting new game is now a button instead of a shortcut keybinding</li>
<li>Cleaned up code a bit and posted on GitHub</li>
<li>Next step: write a blog post (this post!)</li>
</ul>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> Visit <a href="https://github.com/learnbyexample/TUI-apps/tree/main/SquareTicTacToe">my GitHub repo</a> for the code, game rules and other details.</p>
</blockquote>
<p>I had made a <a href="https://learnbyexample.github.io/practice_python_projects/square_tic_tac_toe/square_tic_tac_toe.html">GUI version of this game using tkinter</a> last year. I copied most of the game logic from there, so I didn't much struggle with object-oriented programming in this case. Here's a sample screenshot from the finished code:</p>
<p align="center"><img src="/images/textual/square_tictactoe.png" alt="Sample screenshot for Square Tic Tac Toe game" /></p>
<br>
<h2 id="what-i-liked">What I liked<a class="zola-anchor" href="#what-i-liked" aria-label="Anchor link for: what-i-liked">🔗</a></h2>
<p>As mentioned before, Textual supports <em>live editing</em> mode. The command is <code>textual run --dev script.py</code> and this helps you experiment with CSS. I found this very helpful while trying out layout combinations, margin, padding, etc.</p>
<p>The default colors were great too. I didn't have to think about choosing colors (except for setting background color for header and game status). The framework even provides an easy way to allow users to switch between dark and light themes! Though, I haven't yet figured out how to set light theme as the default (I worked around by explicitly adding a call to the theme toggle method).</p>
<p>Overall, the code was significantly shorter compared to the <code>tkinter</code> version I did last year. That version had a few more features, but I'd say Textual felt much easier to reason about. I remember having to spend days shifting through stackoverflow threads and <a href="https://tkdocs.com/index.html">tkdocs</a> to get the GUI version working.</p>
<br>
<h2 id="what-gave-me-trouble">What gave me trouble<a class="zola-anchor" href="#what-gave-me-trouble" aria-label="Anchor link for: what-gave-me-trouble">🔗</a></h2>
<p>Struggling with layout isn't new for me. I started with 4x4 grid for the board, which was fairly straightforward. Problems arose when I wanted to add status text area to the left and control buttons to the right. Placing them left/right was easy to do with <em>dock</em> in CSS. But, I couldn't get them to align well — too much spacing around the 4x4 board. I was trying to give 50% to 60% for the board and the remaining evenly divided for the other two elements. After some experimentation, what worked was giving 20% to status, 25% to control and <em>not</em> assigning a width value for the board.</p>
<p>I initially used a button for the status because I couldn't find a <em>textbox</em> widget (edit: Textual now has a <code>Label</code> widget). I knew that <a href="https://textual.textualize.io/widgets/static/">Static</a> widget can display text, but I didn't find how to dynamically change the text from that documentation page. I thought I'll have to make a custom widget, but when I went to <a href="https://textual.textualize.io/guide/widgets/">Widgets guide</a>, I found that <code>Static</code> already has an <code>update()</code> method!</p>
<p>I probably missed something (or perhaps part of the roadmap), but I found it strange to have a single <code>on_button_pressed()</code> method to handle on click event for every <code>Button</code> widget. I'd prefer a way to bind a method to the buttons, like <code>tkinter</code> provides.</p>
<br>
<h2 id="next-steps">Next steps<a class="zola-anchor" href="#next-steps" aria-label="Anchor link for: next-steps">🔗</a></h2>
<p>As mentioned before, I have several projects in my todo list. The next one I want to try is an app for interactive exercises for <a href="https://learnbyexample.github.io/books/">my ebooks</a>. Last year, I made one for <a href="https://github.com/learnbyexample/py_regular_expressions/tree/master/interactive_exercises">Python regular expressions</a> using <code>tkinter</code>.</p>
Computing from the Command Line: sales report2022-11-14T00:00:00+00:002022-11-14T00:00:00+00:00https://learnbyexample.github.io/mini/cli-computing-sales/<p>I've previously written about events and strategies that led to <a href="https://learnbyexample.github.io/wild-ride-2021/#book-sales">increased ebook sales during the last quarter of 2021</a>.</p>
<p>Very pleased to inform that I continue to see more than expected sales during release week. My 13th ebook <a href="https://github.com/learnbyexample/cli-computing">Computing from the Command Line</a> was published on November 1st. Here's how the sales looked on Gumroad during the first ten days:</p>
<p align="center"><img src="/images/cli-computing-release-gumroad-sales.png" alt="Ten days Gumroad sales chart" /></p>
<p>I used to offer my ebooks for free on release. For the past few releases, I have also added heavily discounted ebook bundles which seems to be the major factor in increased paid sales I'm seeing. Luck certainly plays a role too in reaching users through social media. Here are some of the ways I promoted my latest ebook:</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/p/announcing-computing-from-the-command-line-free-discount-offers-and-more">Announcement post on Gumroad</a> and sending an email to existing readers (1000+ users opened the email as per Gumroad analytics)</li>
<li><a href="https://twitter.com/learn_byexample/status/1587443517823275009">Pinned tweet</a> — more than 300 link clicks as per Twitter analytics</li>
<li>Posting on <a href="https://old.reddit.com/r/commandline/comments/yk1izp/i_wrote_a_book_on_linux_cli_tools_and_shell/">/r/commandline/</a>, <a href="https://old.reddit.com/r/linux/comments/yk1n8y/i_wrote_a_book_on_linux_cli_tools_and_shell/">/r/linux/</a>, <a href="https://old.reddit.com/r/linux4noobs/comments/ykzuia/i_wrote_a_book_on_linux_cli_tools_and_shell/">/r/linux4noobs/</a> and <a href="https://old.reddit.com/r/FreeEBOOKS/comments/yoj33g/computing_from_the_command_line_linux_tools_and/">/r/FreeEBOOKS/</a></li>
<li><a href="https://news.ycombinator.com/item?id=33449401">Show HN post on Hacker News</a> — wasn't lucky this time to reach front page</li>
<li><a href="https://youtu.be/PS5XEemn164">Promo video on youtube</a></li>
<li>Mentioned in my <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> newsletter</li>
<li>And of course, I wrote a release post <a href="https://learnbyexample.github.io/computing-from-the-command-line-announcement/">on this blog</a> and also mentioned it on my <a href="https://github.com/learnbyexample">GitHub Readme</a></li>
</ul>
<p>Apart from Gumroad, 400+ readers downloaded the ebook from <a href="https://leanpub.com/cli_computing">Leanpub</a> and I got a few paid sales as well. I wrote about <a href="https://learnbyexample.github.io/my-book-writing-experience/#leanpub-vs-gumroad">pros and cons of Gumroad/Leanpub here</a>.</p>
<hr />
<p><img src="/images/info.svg" alt="info" /> PS: Make sure to read the rules and be a regular user before self-promoting your content on the social media platforms mentioned above.</p>
Vim tip 17: setting options2022-11-08T00:00:00+00:002022-11-08T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-17/<p>From <a href="https://vimhelp.org/options.txt.html">:h options.txt</a>:</p>
<blockquote>
<p>Vim has a number of internal variables and switches which can be set to achieve special effects. These options come in three forms:</p>
<ul>
<li><strong>boolean</strong> can only be on or off</li>
<li><strong>number</strong> has a numeric value</li>
<li><strong>string</strong> has a string value</li>
</ul>
</blockquote>
<p>Here are examples for each of these forms:</p>
<ul>
<li><kbd>:set cursorline</kbd> highlight the line containing the cursor</li>
<li><kbd>:set history=200</kbd> increase default history from 50 to 200</li>
<li><kbd>:set ww+=[,]</kbd> allow left and right arrow keys to move across lines in Insert mode
<ul>
<li><code>+=</code> allows you to append to an existing string value</li>
</ul>
</li>
</ul>
<p>Usage guidelines:</p>
<ul>
<li><code>set {option}</code> switch on the given boolean setting
<ul>
<li><kbd>:set expandtab</kbd> use spaces for tab expansion</li>
</ul>
</li>
<li><code>set {option}!</code> toggle the given boolean setting
<ul>
<li><kbd>:set expandtab!</kbd> if previously tabs were expanded, it will be turned off and vice versa</li>
<li><code>set inv{option}</code> can also be used</li>
</ul>
</li>
<li><code>set no{option}</code> switch off the given boolean setting
<ul>
<li><kbd>:set noexpandtab</kbd> disable expanding tab to spaces</li>
</ul>
</li>
<li><code>set {option}?</code> get the current value of the given option (works for all three forms)
<ul>
<li><kbd>:set expandtab?</kbd> output will be <code>expandtab</code> or <code>noexpandtab</code> depending on whether it is switched on or off</li>
</ul>
</li>
<li><code>set {option}</code> get the current value of number or string option
<ul>
<li>for example, try <kbd>:set history</kbd> or <kbd>:set ww</kbd></li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/options.txt.html">:h options.txt</a> for complete list of usage guidelines and available options.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/hjHEn7t2zUM" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 18: inserting file contents using GNU sed2022-11-02T00:00:00+00:002022-11-02T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-18/<p>The <code>r</code> command accepts a filename as argument and when the address is satisfied, entire contents of the given file is added <em>after</em> the matching line. This is a robust way to add multiline text literally.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat ip.txt
</span><span> </span><span style="color:#72ab00;">*</span><span> sky
</span><span> </span><span style="color:#72ab00;">*</span><span> apple
</span><span>$ cat fav_colors.txt
</span><span>deep red
</span><span>yellow
</span><span>reddish
</span><span>brown
</span><span>
</span><span style="color:#7f8989;"># space between r and filename is optional
</span><span style="color:#7f8989;"># adds entire contents of 'ip.txt' after each line containing 'red'
</span><span>$ sed </span><span style="color:#d07711;">'/red/r ip.txt'</span><span> fav_colors.txt
</span><span>deep red
</span><span> </span><span style="color:#72ab00;">*</span><span> sky
</span><span> </span><span style="color:#72ab00;">*</span><span> apple
</span><span>yellow
</span><span>reddish
</span><span> </span><span style="color:#72ab00;">*</span><span> sky
</span><span> </span><span style="color:#72ab00;">*</span><span> apple
</span><span>brown
</span></code></pre>
<p>The <code>e</code> flag is the easiest way to insert file contents <em>before</em> the matching lines. Similar to the <code>r</code> command, the output of an external command (<code>cat</code> in the below example) is inserted literally.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ sed </span><span style="color:#d07711;">'/red/e cat ip.txt'</span><span> fav_colors.txt
</span><span> </span><span style="color:#72ab00;">*</span><span> sky
</span><span> </span><span style="color:#72ab00;">*</span><span> apple
</span><span>deep red
</span><span>yellow
</span><span> </span><span style="color:#72ab00;">*</span><span> sky
</span><span> </span><span style="color:#72ab00;">*</span><span> apple
</span><span>reddish
</span><span>brown
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://learnbyexample.github.io/learn_gnused/adding-content-from-file.html">Adding content from file</a> chapter from my <strong>GNU sed</strong> ebook for many more examples, gotchas, details about the <code>R</code> command and so on.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/p1CFOCs3gGM" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/learn_gnused">CLI text processing with GNU sed</a> ebook.</p>
Python tip 18: arbitrary number of arguments2022-10-26T00:00:00+00:002022-10-26T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-18/<p>The <code>print()</code> function can accept zero or more values separated by a comma. Here's how the function arguments are shown in <code>help(print)</code>:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b39f04;">print</span><span>(value, </span><span style="color:#b3933a;">...</span><span>, </span><span style="color:#5597d6;">sep</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">' '</span><span>, </span><span style="color:#5597d6;">end</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#5597d6;">file</span><span style="color:#72ab00;">=</span><span>sys.stdout, </span><span style="color:#5597d6;">flush</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">False</span><span>)
</span></code></pre>
<p>Here are some examples with varying number of arguments passed to the <code>print()</code> function:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">print</span><span>()
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">print</span><span>(</span><span style="color:#d07711;">'hello'</span><span>)
</span><span>hello
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">print</span><span>(</span><span style="color:#b3933a;">42</span><span>, </span><span style="color:#b3933a;">22</span><span style="color:#72ab00;">/</span><span style="color:#b3933a;">7</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">100</span><span>)
</span><span style="color:#b3933a;">42 3.142857142857143 </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">100
</span></code></pre>
<p>You can write your own functions to accept arbitrary number of arguments as well. The packing syntax is similar to <a href="https://learnbyexample.github.io/tips/python-tip-14/">sequence unpacking</a>. A <code>*</code> prefix to an argument name will allow it to accept zero or more values. Such an argument will be packed as a <code>tuple</code> data type and it should always be specified after positional arguments (if any). <code>args</code> is often used as the variable name for this purpose. Here's an example:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span style="background-color:#562d56bf;color:#f8f8f8;">def</span><span> </span><span style="color:#5597d6;">many</span><span>(x, </span><span style="color:#72ab00;">*</span><span>args):
</span><span style="color:#b3933a;">... </span><span style="color:#b39f04;">print</span><span>(</span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{x = }</span><span style="color:#d07711;">; </span><span>{args = }</span><span style="color:#d07711;">'</span><span>)
</span><span style="color:#b3933a;">...
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">many</span><span>()
</span><span style="color:#5597d6;">Traceback </span><span>(most recent call last):
</span><span> File </span><span style="color:#d07711;">"<stdin>"</span><span>, line </span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#72ab00;">in <</span><span>module</span><span style="color:#72ab00;">>
</span><span style="color:#a2a001;">TypeError</span><span>: </span><span style="color:#5597d6;">many</span><span>() missing </span><span style="color:#b3933a;">1 </span><span>required positional argument: </span><span style="color:#d07711;">'x'
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">many</span><span>(</span><span style="color:#b3933a;">1</span><span>)
</span><span>x </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">1</span><span>; args </span><span style="color:#72ab00;">= </span><span>()
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">many</span><span>(</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#d07711;">'two'</span><span>, </span><span style="color:#b3933a;">3</span><span>)
</span><span>x </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">1</span><span>; args </span><span style="color:#72ab00;">= </span><span>(</span><span style="color:#d07711;">'two'</span><span>, </span><span style="color:#b3933a;">3</span><span>)
</span></code></pre>
<p>Here's a more practical example:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span style="background-color:#562d56bf;color:#f8f8f8;">def</span><span> </span><span style="color:#5597d6;">sum_nums</span><span>(</span><span style="color:#72ab00;">*</span><span>args):
</span><span style="color:#b3933a;">... </span><span>total </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">0
</span><span style="color:#b3933a;">... </span><span style="color:#72ab00;">for </span><span>n </span><span style="color:#72ab00;">in </span><span>args:
</span><span style="color:#b3933a;">... </span><span>total </span><span style="color:#72ab00;">+= </span><span>n
</span><span style="color:#b3933a;">... </span><span style="color:#72ab00;">return </span><span>total
</span><span style="color:#b3933a;">...
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">sum_nums</span><span>()
</span><span style="color:#b3933a;">0
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">sum_nums</span><span>(</span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">8</span><span>)
</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">5
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">sum_nums</span><span>(</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">4</span><span>, </span><span style="color:#b3933a;">5</span><span>)
</span><span style="color:#b3933a;">15
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">sum_nums</span><span>(</span><span style="color:#72ab00;">*</span><span style="color:#b39f04;">range</span><span>(</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">6</span><span>))
</span><span style="color:#b3933a;">15
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> Use <code>**</code> prefix to accept arbitrary number of keyword arguments. See also <a href="https://docs.python.org/3/tutorial/controlflow.html#arbitrary-argument-lists">docs.python: Arbitrary Argument Lists</a>.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/kp0TQgguiBI" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 16: terminal mode2022-10-18T00:00:00+00:002022-10-18T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-16/<p>Terminal mode is one way to use shell commands from within Vim.</p>
<ul>
<li><kbd>:terminal</kbd> open a new terminal window as a horizontal split
<ul>
<li>opens above the current window unless <code>splitbelow</code> option is set</li>
</ul>
</li>
<li><kbd>:vertical :terminal</kbd> open a new terminal window as a vertical split
<ul>
<li>opens to the left of the current window unless <code>splitright</code> option is set</li>
</ul>
</li>
</ul>
<p>Here are some shortcuts to navigate between windows and change modes:</p>
<ul>
<li><kbd>Ctrl</kbd>+<kbd>w</kbd> followed by <kbd>w</kbd> or <kbd>Ctrl</kbd>+<kbd>w</kbd> move to the next window
<ul>
<li>helps you to easily switch back and forth if you have one text editing window and one terminal window</li>
<li>see the <a href="https://learnbyexample.github.io/tips/vim-tip-14/">Splitting</a> tip for more such commands</li>
</ul>
</li>
<li><kbd>Ctrl</kbd>+<kbd>w</kbd> followed by <kbd>N</kbd> goes to Terminal-Normal mode which will help you to move around using Normal mode commands, copy text, etc (note that you need to use uppercase <code>N</code> here)
<ul>
<li><kbd>Ctrl</kbd>+<kbd>\</kbd> followed by <kbd>Ctrl</kbd>+<kbd>n</kbd> another way to go to Terminal-Normal mode</li>
<li><kbd>:tnoremap <Esc> <C-w>N</kbd> map <kbd>Esc</kbd> key to go to Terminal-Normal mode</li>
</ul>
</li>
<li><kbd>Ctrl</kbd>+<kbd>w</kbd> followed by <kbd>:</kbd> go to Command-line mode from terminal window</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> Depending on your shell, you can use the <code>exit</code> command to end the terminal session. <code>Ctrl+d</code> might work too.</p>
<p><img src="/images/info.svg" alt="info" /> There are lot of features in this mode, see <a href="https://vimhelp.org/terminal.txt.html">:h terminal.txt</a> for more details.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/fuBCeOEE1jo" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 17: common and unique lines2022-10-12T00:00:00+00:002022-10-12T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-17/<p>Consider these sample input files that are already sorted and the default output from <code>comm</code>:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ paste colors_1.txt colors_2.txt
</span><span style="color:#5597d6;">Blue Black
</span><span style="color:#5597d6;">Brown Blue
</span><span style="color:#5597d6;">Orange Green
</span><span style="color:#5597d6;">Purple Orange
</span><span style="color:#5597d6;">Red Pink
</span><span style="color:#5597d6;">Teal Red
</span><span style="color:#5597d6;">White White
</span><span>
</span><span>$ comm colors_1.txt colors_2.txt
</span><span> </span><span style="color:#5597d6;">Black
</span><span> </span><span style="color:#5597d6;">Blue
</span><span style="color:#5597d6;">Brown
</span><span> </span><span style="color:#5597d6;">Green
</span><span> </span><span style="color:#5597d6;">Orange
</span><span> </span><span style="color:#5597d6;">Pink
</span><span style="color:#5597d6;">Purple
</span><span> </span><span style="color:#5597d6;">Red
</span><span style="color:#5597d6;">Teal
</span><span> </span><span style="color:#5597d6;">White
</span></code></pre>
<p>The following <code>comm</code> options will help you construct solutions to get common and unique lines:</p>
<ul>
<li><code>-1</code> suppress lines unique to the first file</li>
<li><code>-2</code> suppress lines unique to the second file</li>
<li><code>-3</code> suppress lines common to both the files</li>
</ul>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># common lines
</span><span>$ comm </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">12</span><span> colors_1.txt colors_2.txt
</span><span style="color:#5597d6;">Blue
</span><span style="color:#5597d6;">Orange
</span><span style="color:#5597d6;">Red
</span><span style="color:#5597d6;">White
</span><span>
</span><span style="color:#7f8989;"># lines unique to colors_2.txt
</span><span>$ comm </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">13</span><span> colors_1.txt colors_2.txt
</span><span style="color:#5597d6;">Black
</span><span style="color:#5597d6;">Green
</span><span style="color:#5597d6;">Pink
</span></code></pre>
<p>If the input files are not already sorted, or if you want to preserve the order of input lines, you can use <code>awk</code> instead:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># common lines
</span><span>$ awk </span><span style="color:#d07711;">'NR==FNR{a[$0]; next} $0 in a'</span><span> colors_1.txt colors_2.txt
</span><span style="color:#5597d6;">Blue
</span><span style="color:#5597d6;">Orange
</span><span style="color:#5597d6;">Red
</span><span style="color:#5597d6;">White
</span><span>
</span><span style="color:#7f8989;"># lines unique to colors_2.txt
</span><span>$ awk </span><span style="color:#d07711;">'NR==FNR{a[$0]; next} !($0 in a)'</span><span> colors_1.txt colors_2.txt
</span><span style="color:#5597d6;">Black
</span><span style="color:#5597d6;">Green
</span><span style="color:#5597d6;">Pink
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> You can also use <code>grep -Fxf colors_1.txt colors_2.txt</code> (add <code>-v</code> for unique lines) but this wouldn't scale well for larger input files.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/LbAZMZteDpw" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/cli-computing">Linux Command Line Computing</a> ebook.</p>
Python tip 17: counting frequency of items2022-10-06T00:00:00+00:002022-10-06T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-17/<p>One of the ways to count the frequency of items is to make use of the <code>dict.get()</code> method:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>vehicles </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'car'</span><span>, </span><span style="color:#d07711;">'jeep'</span><span>, </span><span style="color:#d07711;">'car'</span><span>, </span><span style="color:#d07711;">'bike'</span><span>, </span><span style="color:#d07711;">'bus'</span><span>, </span><span style="color:#d07711;">'car'</span><span>, </span><span style="color:#d07711;">'bike'</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>hist </span><span style="color:#72ab00;">= </span><span>{}
</span><span style="color:#72ab00;">>>> for </span><span>v </span><span style="color:#72ab00;">in </span><span>vehicles:
</span><span style="color:#b3933a;">... </span><span>hist[v] </span><span style="color:#72ab00;">= </span><span>hist.</span><span style="color:#5597d6;">get</span><span>(v, </span><span style="color:#b3933a;">0</span><span>) </span><span style="color:#72ab00;">+ </span><span style="color:#b3933a;">1
</span><span style="color:#b3933a;">...
</span><span style="color:#72ab00;">>>> </span><span>hist
</span><span>{</span><span style="color:#d07711;">'car'</span><span>: </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#d07711;">'jeep'</span><span>: </span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#d07711;">'bike'</span><span>: </span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#d07711;">'bus'</span><span>: </span><span style="color:#b3933a;">1</span><span>}
</span></code></pre>
<p>And here's a solution using the built-in <a href="https://docs.python.org/3/library/collections.html">collections</a> module:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> from </span><span>collections </span><span style="color:#72ab00;">import </span><span>Counter
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>vehicles </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'car'</span><span>, </span><span style="color:#d07711;">'jeep'</span><span>, </span><span style="color:#d07711;">'car'</span><span>, </span><span style="color:#d07711;">'bike'</span><span>, </span><span style="color:#d07711;">'bus'</span><span>, </span><span style="color:#d07711;">'car'</span><span>, </span><span style="color:#d07711;">'bike'</span><span>]
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">Counter</span><span>(vehicles)
</span><span style="color:#5597d6;">Counter</span><span>({</span><span style="color:#d07711;">'car'</span><span>: </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#d07711;">'bike'</span><span>: </span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#d07711;">'jeep'</span><span>: </span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#d07711;">'bus'</span><span>: </span><span style="color:#b3933a;">1</span><span>})
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">Counter</span><span>(</span><span style="color:#d07711;">'abracadabra'</span><span>)
</span><span style="color:#5597d6;">Counter</span><span>({</span><span style="color:#d07711;">'a'</span><span>: </span><span style="color:#b3933a;">5</span><span>, </span><span style="color:#d07711;">'b'</span><span>: </span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#d07711;">'r'</span><span>: </span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#d07711;">'c'</span><span>: </span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#d07711;">'d'</span><span>: </span><span style="color:#b3933a;">1</span><span>})
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://stackoverflow.com/q/3496518/4082052">stackoverflow: using a dictionary to count items</a> and <a href="https://stackoverflow.com/q/2161752/4082052">stackoverflow: count frequency of elements</a> for more ways to solve this problem.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/Vv950-4CL-E" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 15: moving within current line2022-09-27T00:00:00+00:002022-09-27T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-15/<p>Here are some of the Normal mode commands for moving within the current line:</p>
<ul>
<li><kbd>0</kbd> move to the beginning of the current line (i.e. column number 1)
<ul>
<li>you can also use the <kbd>Home</kbd> key</li>
</ul>
</li>
<li><kbd>^</kbd> move to the beginning of the first non-blank character of the current line (useful for indented lines)</li>
<li><kbd>$</kbd> move to the end of the current line
<ul>
<li>you can also use the <kbd>End</kbd> key</li>
<li><kbd>3$</kbd> move to the end of 2 lines below the current line</li>
</ul>
</li>
<li><kbd>g_</kbd> move to the last non-blank character of the current line</li>
<li><kbd>3|</kbd> move to the third column character
<ul>
<li><kbd>|</kbd> is same as <kbd>0</kbd> or <kbd>1|</kbd></li>
</ul>
</li>
</ul>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/VfN_LJIiyaI" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 16: transpose tables2022-09-21T00:00:00+00:002022-09-21T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-16/<p><a href="https://www.gnu.org/software/datamash/">GNU datamash</a> has plenty of nifty features for field based operations. Here's an example of transposing comma delimited data:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat scores.csv
</span><span style="color:#5597d6;">Name</span><span>,</span><span style="color:#5597d6;">Maths</span><span>,</span><span style="color:#5597d6;">Physics</span><span>,</span><span style="color:#5597d6;">Chemistry
</span><span style="color:#5597d6;">Ith</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">100
</span><span style="color:#5597d6;">Cy</span><span>,</span><span style="color:#b3933a;">97</span><span>,</span><span style="color:#b3933a;">98</span><span>,</span><span style="color:#b3933a;">95
</span><span style="color:#5597d6;">Lin</span><span>,</span><span style="color:#b3933a;">78</span><span>,</span><span style="color:#b3933a;">83</span><span>,</span><span style="color:#b3933a;">80
</span><span style="color:#5597d6;">Er</span><span>,</span><span style="color:#b3933a;">60</span><span>,</span><span style="color:#b3933a;">70</span><span>,</span><span style="color:#b3933a;">90
</span><span>
</span><span>$ datamash </span><span style="color:#72ab00;">-</span><span>t, transpose </span><span style="color:#72ab00;"><</span><span>scores.csv
</span><span style="color:#5597d6;">Name</span><span>,</span><span style="color:#5597d6;">Ith</span><span>,</span><span style="color:#5597d6;">Cy</span><span>,</span><span style="color:#5597d6;">Lin</span><span>,</span><span style="color:#5597d6;">Er
</span><span style="color:#5597d6;">Maths</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">97</span><span>,</span><span style="color:#b3933a;">78</span><span>,</span><span style="color:#b3933a;">60
</span><span style="color:#5597d6;">Physics</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">98</span><span>,</span><span style="color:#b3933a;">83</span><span>,</span><span style="color:#b3933a;">70
</span><span style="color:#5597d6;">Chemistry</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">95</span><span>,</span><span style="color:#b3933a;">80</span><span>,</span><span style="color:#b3933a;">90
</span></code></pre>
<p>And here's an alternate solution using <code>tr</code>, <code>wc</code> and <code>pr</code>:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># divide input into five parts and join them vertically
</span><span>$ seq </span><span style="color:#b3933a;">10 </span><span style="color:#72ab00;">|</span><span> pr </span><span style="color:#72ab00;">-</span><span>5ts,
</span><span style="color:#b3933a;">1</span><span>,</span><span style="color:#b3933a;">3</span><span>,</span><span style="color:#b3933a;">5</span><span>,</span><span style="color:#b3933a;">7</span><span>,</span><span style="color:#b3933a;">9
</span><span style="color:#b3933a;">2</span><span>,</span><span style="color:#b3933a;">4</span><span>,</span><span style="color:#b3933a;">6</span><span>,</span><span style="color:#b3933a;">8</span><span>,</span><span style="color:#b3933a;">10
</span><span>
</span><span style="color:#7f8989;"># tr converts input table into single field per line
</span><span style="color:#7f8989;"># wc calculates number of rows and pr does the rest
</span><span>$ tr </span><span style="color:#d07711;">',' '\n' </span><span style="color:#72ab00;"><</span><span>scores.csv </span><span style="color:#72ab00;">|</span><span> pr </span><span style="color:#72ab00;">-</span><span>$(wc </span><span style="color:#72ab00;">-</span><span>l </span><span style="color:#72ab00;"><</span><span>scores.csv)ts,
</span><span style="color:#5597d6;">Name</span><span>,</span><span style="color:#5597d6;">Ith</span><span>,</span><span style="color:#5597d6;">Cy</span><span>,</span><span style="color:#5597d6;">Lin</span><span>,</span><span style="color:#5597d6;">Er
</span><span style="color:#5597d6;">Maths</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">97</span><span>,</span><span style="color:#b3933a;">78</span><span>,</span><span style="color:#b3933a;">60
</span><span style="color:#5597d6;">Physics</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">98</span><span>,</span><span style="color:#b3933a;">83</span><span>,</span><span style="color:#b3933a;">70
</span><span style="color:#5597d6;">Chemistry</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">95</span><span>,</span><span style="color:#b3933a;">80</span><span>,</span><span style="color:#b3933a;">90
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://unix.stackexchange.com/q/308631/109046">unix.stackexchange: How to process an x-column text file to get a y-column one?</a> for many more ways to deal with such problems. See <a href="https://learnbyexample.github.io/cli-computation-gnu-datamash/">my blog post</a> for examples and resource links on the GNU datamash command.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/vFMMjTICMJg" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/cli-computing">Linux Command Line Computing</a> ebook.</p>
Python tip 16: delete list elements using index or slice2022-09-14T00:00:00+00:002022-09-14T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-16/<p>The <code>pop()</code> method removes the last element of a <code>list</code> by default. You can pass an index to delete that specific item and the list will be automatically re-arranged. Return value is the element being deleted.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>primes </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">5</span><span>, </span><span style="color:#b3933a;">7</span><span>, </span><span style="color:#b3933a;">11</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>primes.</span><span style="color:#5597d6;">pop</span><span>()
</span><span style="color:#b3933a;">11
</span><span style="color:#72ab00;">>>> </span><span>primes
</span><span>[</span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">5</span><span>, </span><span style="color:#b3933a;">7</span><span>]
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>student </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'learnbyexample'</span><span>, </span><span style="color:#b3933a;">2022</span><span>, [</span><span style="color:#d07711;">'Linux'</span><span>, </span><span style="color:#d07711;">'Vim'</span><span>, </span><span style="color:#d07711;">'Python'</span><span>]]
</span><span style="color:#72ab00;">>>> </span><span>student.</span><span style="color:#5597d6;">pop</span><span>(</span><span style="color:#b3933a;">1</span><span>)
</span><span style="color:#b3933a;">2022
</span><span style="color:#72ab00;">>>> </span><span>student[</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">1</span><span>].</span><span style="color:#5597d6;">pop</span><span>(</span><span style="color:#b3933a;">1</span><span>)
</span><span style="color:#d07711;">'Vim'
</span><span style="color:#72ab00;">>>> </span><span>student
</span><span>[</span><span style="color:#d07711;">'learnbyexample'</span><span>, [</span><span style="color:#d07711;">'Linux'</span><span>, </span><span style="color:#d07711;">'Python'</span><span>]]
</span></code></pre>
<p>To remove multiple elements using slicing notation, use the <code>del</code> statement. Unlike the <code>pop()</code> method, you won't get the elements being deleted as the return value.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>books </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'cradle'</span><span>, </span><span style="color:#d07711;">'mistborn'</span><span>, </span><span style="color:#d07711;">'legends & lattes'</span><span>, </span><span style="color:#d07711;">'sourdough'</span><span>]
</span><span style="color:#72ab00;">>>> del </span><span>books[</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">1</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>books
</span><span>[</span><span style="color:#d07711;">'cradle'</span><span>, </span><span style="color:#d07711;">'mistborn'</span><span>, </span><span style="color:#d07711;">'legends & lattes'</span><span>]
</span><span style="color:#72ab00;">>>> del </span><span>books[:</span><span style="color:#b3933a;">2</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>books
</span><span>[</span><span style="color:#d07711;">'legends & lattes'</span><span>]
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>student </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'learnbyexample'</span><span>, </span><span style="color:#b3933a;">2022</span><span>, [</span><span style="color:#d07711;">'Linux'</span><span>, </span><span style="color:#d07711;">'Vim'</span><span>, </span><span style="color:#d07711;">'Python'</span><span>]]
</span><span style="color:#72ab00;">>>> del </span><span>student[</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">1</span><span>][</span><span style="color:#b3933a;">1</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>student
</span><span>[</span><span style="color:#d07711;">'learnbyexample'</span><span>, </span><span style="color:#b3933a;">2022</span><span>, [</span><span style="color:#d07711;">'Linux'</span><span>, </span><span style="color:#d07711;">'Python'</span><span>]]
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/mLYVt8Nmxv8" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 14: horizontal and vertical splits2022-09-06T00:00:00+00:002022-09-06T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-14/<p>You can have multiple windows within the same tab page.</p>
<ul>
<li><kbd>:split filename</kbd> open file for editing in a new horizontal window, above the current window
<ul>
<li>you can also use <kbd>:sp</kbd> instead of <kbd>:split</kbd></li>
<li><kbd>:set splitbelow</kbd> open horizontal splits below the current window</li>
</ul>
</li>
<li><kbd>:vsplit filename</kbd> open file for editing in a new vertical window, to the left of the current window
<ul>
<li>you can also use <kbd>:vs</kbd> instead of <kbd>:vsplit</kbd></li>
<li><kbd>:set splitright</kbd> open vertical splits to the right of the current window</li>
</ul>
</li>
</ul>
<p>Here are some shortcuts to navigate between windows:</p>
<ul>
<li><kbd>Ctrl</kbd>+<kbd>w</kbd> followed by <kbd>w</kbd> switch to the below/right window for horizontal/vertical splits respectively
<ul>
<li><kbd>Ctrl</kbd>+<kbd>w</kbd> followed by <kbd>Ctrl</kbd>+<kbd>w</kbd> also performs the same function</li>
<li>switches to the first split if you are on the last split</li>
</ul>
</li>
<li><kbd>Ctrl</kbd>+<kbd>w</kbd> followed by <kbd>W</kbd> switch to the above/left window for horizontal/vertical splits respectively
<ul>
<li>switches to the last split if you are on the first split</li>
</ul>
</li>
<li><kbd>Ctrl</kbd>+<kbd>w</kbd> followed by <code>hjkl</code> or arrow keys, switch in the respective direction</li>
<li><kbd>Ctrl</kbd>+<kbd>w</kbd> followed by <kbd>t</kbd> or <kbd>b</kbd> switch to the top (first) or bottom (last) window</li>
<li><kbd>Ctrl</kbd>+<kbd>w</kbd> followed by <code>HJKL</code> (uppercase), moves the current split to the farthest possible location in the respective direction</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> If filename is not provided, the current one is used.</p>
<p><img src="/images/info.svg" alt="info" /> Vim adds a highlighted horizontal bar containing the filename for each split.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/6xtHzEdFtFs" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 15: text generation with printf and brace expansion2022-08-31T00:00:00+00:002022-08-31T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-15/<p>You can use <a href="https://www.gnu.org/software/bash/manual/bash.html#Brace-Expansion">brace expansion</a> for generating a sequence of numbers and alphabets. <code>printf</code> helps you to display multiple arguments using the same format specifier. For example:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ echo {</span><span style="color:#b3933a;">1</span><span style="color:#72ab00;">..</span><span style="color:#b3933a;">3</span><span>}
</span><span style="color:#b3933a;">1 2 3
</span><span>$ echo {</span><span style="color:#b3933a;">1</span><span style="color:#72ab00;">..</span><span style="color:#b3933a;">2</span><span>}{a</span><span style="color:#72ab00;">..</span><span>b}
</span><span>1a 1b 2a 2b
</span><span>
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%s</span><span style="color:#d07711;">\n'</span><span> apple banana cherry
</span><span>apple
</span><span>banana
</span><span>cherry
</span></code></pre>
<p>Combining the two, you can generate multiple lines of text. Here are some examples:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%s</span><span style="color:#d07711;">\n'</span><span> id_{</span><span style="color:#b3933a;">3</span><span style="color:#72ab00;">..</span><span style="color:#b3933a;">1</span><span>}
</span><span>id_3
</span><span>id_2
</span><span>id_1
</span><span>
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%s</span><span style="color:#d07711;">\n'</span><span> item_{</span><span style="color:#b3933a;">100</span><span style="color:#72ab00;">..</span><span style="color:#b3933a;">120</span><span style="color:#72ab00;">..</span><span style="color:#b3933a;">4</span><span>}
</span><span>item_100
</span><span>item_104
</span><span>item_108
</span><span>item_112
</span><span>item_116
</span><span>item_120
</span></code></pre>
<p>Here's a practical example:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># the string before %.s is repeated based on the number of arguments
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'x </span><span style="color:#aeb52b;">%.s</span><span style="color:#d07711;">'</span><span> a b c
</span><span>x x x
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#72ab00;">-- </span><span style="color:#d07711;">'- </span><span style="color:#aeb52b;">%.s</span><span style="color:#d07711;">' </span><span>{</span><span style="color:#b3933a;">1</span><span style="color:#72ab00;">..</span><span style="color:#b3933a;">5</span><span>}
</span><span style="color:#72ab00;">- - - - -
</span><span>
</span><span style="color:#7f8989;"># same as: seq 10 | paste -d, - - - - -
</span><span>$ seq </span><span style="color:#b3933a;">10 </span><span style="color:#72ab00;">|</span><span> paste </span><span style="color:#72ab00;">-</span><span>d, $(</span><span style="color:#b39f04;">printf </span><span style="color:#72ab00;">-- </span><span style="color:#d07711;">'- </span><span style="color:#aeb52b;">%.s</span><span style="color:#d07711;">' </span><span>{</span><span style="color:#b3933a;">1</span><span style="color:#72ab00;">..</span><span style="color:#b3933a;">5</span><span>})
</span><span style="color:#b3933a;">1</span><span>,</span><span style="color:#b3933a;">2</span><span>,</span><span style="color:#b3933a;">3</span><span>,</span><span style="color:#b3933a;">4</span><span>,</span><span style="color:#b3933a;">5
</span><span style="color:#b3933a;">6</span><span>,</span><span style="color:#b3933a;">7</span><span>,</span><span style="color:#b3933a;">8</span><span>,</span><span style="color:#b3933a;">9</span><span>,</span><span style="color:#b3933a;">10
</span><span>
</span><span>$ n=</span><span style="color:#b3933a;">5
</span><span>$ seq </span><span style="color:#b3933a;">10 </span><span style="color:#72ab00;">|</span><span> paste </span><span style="color:#72ab00;">-</span><span>d, $(</span><span style="color:#b39f04;">printf </span><span style="color:#72ab00;">-- </span><span style="color:#d07711;">'- </span><span style="color:#aeb52b;">%.s</span><span style="color:#d07711;">'</span><span> $(seq </span><span style="color:#5597d6;">$n</span><span>))
</span><span style="color:#b3933a;">1</span><span>,</span><span style="color:#b3933a;">2</span><span>,</span><span style="color:#b3933a;">3</span><span>,</span><span style="color:#b3933a;">4</span><span>,</span><span style="color:#b3933a;">5
</span><span style="color:#b3933a;">6</span><span>,</span><span style="color:#b3933a;">7</span><span>,</span><span style="color:#b3933a;">8</span><span>,</span><span style="color:#b3933a;">9</span><span>,</span><span style="color:#b3933a;">10
</span><span>
</span><span>$ n=</span><span style="color:#b3933a;">2
</span><span>$ seq </span><span style="color:#b3933a;">10 </span><span style="color:#72ab00;">|</span><span> paste </span><span style="color:#72ab00;">-</span><span>d, $(</span><span style="color:#b39f04;">printf </span><span style="color:#72ab00;">-- </span><span style="color:#d07711;">'- </span><span style="color:#aeb52b;">%.s</span><span style="color:#d07711;">'</span><span> $(seq </span><span style="color:#5597d6;">$n</span><span>))
</span><span style="color:#b3933a;">1</span><span>,</span><span style="color:#b3933a;">2
</span><span style="color:#b3933a;">3</span><span>,</span><span style="color:#b3933a;">4
</span><span style="color:#b3933a;">5</span><span>,</span><span style="color:#b3933a;">6
</span><span style="color:#b3933a;">7</span><span>,</span><span style="color:#b3933a;">8
</span><span style="color:#b3933a;">9</span><span>,</span><span style="color:#b3933a;">10
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://stackoverflow.com/q/5349718/4082052">this stackoverflow thread</a> for other alternatives, avoiding <code>printf</code> for large numbers, etc.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/Br0sQ-Qj4LQ" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/cli-computing">Linux Command Line Computing</a> ebook.</p>
Python tip 15: string transliteration2022-08-24T00:00:00+00:002022-08-24T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-15/<p>The <code>str.translate()</code> method accepts a table of codepoints (numerical value of a character) mapped to another character or codepoint. Map to <code>None</code> for characters that have to be deleted. You can use the <a href="https://docs.python.org/3/library/functions.html#ord">ord()</a> built-in function to get the codepoint of characters. Or, you can use the <code>str.maketrans()</code> method to generate the mapping for you.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">ord</span><span>(</span><span style="color:#d07711;">'a'</span><span>)
</span><span style="color:#b3933a;">97
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">ord</span><span>(</span><span style="color:#d07711;">'A'</span><span>)
</span><span style="color:#b3933a;">65
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>greeting </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'have a nice day'
</span><span style="color:#7f8989;"># map 'a' to 'A', 'e' to 'E' and 'i' to None
</span><span style="color:#72ab00;">>>> </span><span>greeting.</span><span style="color:#5597d6;">translate</span><span>({</span><span style="color:#b3933a;">97</span><span>: </span><span style="color:#b3933a;">65</span><span>, </span><span style="color:#b3933a;">101</span><span>: </span><span style="color:#d07711;">'E'</span><span>, </span><span style="color:#b3933a;">105</span><span>: </span><span style="color:#b3933a;">None</span><span>})
</span><span style="color:#d07711;">'hAvE A ncE dAy'
</span><span>
</span><span style="color:#7f8989;"># first and second arguments specify the one-to-one mapping of characters
</span><span style="color:#7f8989;"># third argument is optional, specifies characters to be deleted
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">str</span><span>.</span><span style="color:#5597d6;">maketrans</span><span>(</span><span style="color:#d07711;">'ae'</span><span>, </span><span style="color:#d07711;">'AE'</span><span>, </span><span style="color:#d07711;">'i'</span><span>)
</span><span>{</span><span style="color:#b3933a;">97</span><span>: </span><span style="color:#b3933a;">65</span><span>, </span><span style="color:#b3933a;">101</span><span>: </span><span style="color:#b3933a;">69</span><span>, </span><span style="color:#b3933a;">105</span><span>: </span><span style="color:#b3933a;">None</span><span>}
</span><span style="color:#72ab00;">>>> </span><span>greeting.</span><span style="color:#5597d6;">translate</span><span>(</span><span style="color:#a2a001;">str</span><span>.</span><span style="color:#5597d6;">maketrans</span><span>(</span><span style="color:#d07711;">'ae'</span><span>, </span><span style="color:#d07711;">'AE'</span><span>, </span><span style="color:#d07711;">'i'</span><span>))
</span><span style="color:#d07711;">'hAvE A ncE dAy'
</span></code></pre>
<p>The <a href="https://docs.python.org/3/library/string.html">string module</a> has a collection of constants that are often useful in text processing. Here's an example of deleting punctuation characters:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> from </span><span>string </span><span style="color:#72ab00;">import </span><span>punctuation
</span><span style="color:#72ab00;">>>> </span><span>punctuation
</span><span style="color:#d07711;">'!"#$%&</span><span style="color:#aeb52b;">\'</span><span style="color:#d07711;">()*+,-./:;<=>?@[</span><span style="color:#aeb52b;">\\</span><span style="color:#d07711;">]^_`{|}~'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>para </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'"Hi", there! How *are* you? All fine here.'
</span><span style="color:#72ab00;">>>> </span><span>para.</span><span style="color:#5597d6;">translate</span><span>(</span><span style="color:#a2a001;">str</span><span>.</span><span style="color:#5597d6;">maketrans</span><span>(</span><span style="color:#d07711;">''</span><span>, </span><span style="color:#d07711;">''</span><span>, punctuation))
</span><span style="color:#d07711;">'Hi there How are you All fine here'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>chars_to_delete </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">''</span><span>.</span><span style="color:#5597d6;">join</span><span>(</span><span style="color:#a2a001;">set</span><span>(punctuation) </span><span style="color:#72ab00;">- </span><span style="color:#a2a001;">set</span><span>(</span><span style="color:#d07711;">'.!?'</span><span>))
</span><span style="color:#72ab00;">>>> </span><span>para.</span><span style="color:#5597d6;">translate</span><span>(</span><span style="color:#a2a001;">str</span><span>.</span><span style="color:#5597d6;">maketrans</span><span>(</span><span style="color:#d07711;">''</span><span>, </span><span style="color:#d07711;">''</span><span>, chars_to_delete))
</span><span style="color:#d07711;">'Hi there! How are you? All fine here.'
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/FS7nyQOxoeI" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 13: repeat last change2022-08-16T00:00:00+00:002022-08-16T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-13/<p>It is way too easy to repeat the last change you made:</p>
<ul>
<li><kbd>.</kbd> the Normal mode <strong>dot</strong> command repeats the last change
<ul>
<li>you can also use a number prefix to override the count of the last change</li>
</ul>
</li>
</ul>
<p>For example,</p>
<ul>
<li>if the last change was <kbd>2dd</kbd> (delete current line and the line below), dot key will repeat <code>2dd</code>
<ul>
<li>using <kbd>3.</kbd> will mean <code>3dd</code> and not <code>6dd</code>, since the count prefix replaces the earlier number</li>
</ul>
</li>
<li>if the last change was <kbd>5x</kbd> (delete current character and four characters to the right), dot key will repeat <code>5x</code></li>
<li>if the last change was <kbd>C123<Esc></kbd> and dot key is pressed, it will clear from the current character to the end of the line, insert <code>123</code> and go back to Normal mode</li>
</ul>
<p>From <a href="https://vimhelp.org/usr_04.txt.html#04.3">:h 4.3</a>:</p>
<blockquote>
<p>The <kbd>.</kbd> command works for all changes you make, except for <kbd>u</kbd> (undo), <kbd>CTRL-R</kbd> (redo) and commands that start with a colon (<code>:</code>).</p>
</blockquote>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/repeat.txt.html">:h repeat.txt</a> for complex repeats, using Vim scripts, etc.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/9JrIcNS0eow" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
Programming ebooks by Sundeep Agarwal2022-08-09T00:00:00+00:002025-07-10T00:00:00+00:00https://learnbyexample.github.io/books/<p>This post lists my programming ebooks with details like PDF/EPUB purchase links, GitHub repos, web versions, testimonials, etc. All my ebooks are self-published. You can get these ebooks individually or as part of bundles. You can also read them online for free.</p>
<h1 id="bundles-books"><p style="color: #c05b4d">Bundles 📚</h1>
<p align="center"><a href="https://learnbyexample.gumroad.com/l/all-books"><img src="/images/books/all_books_bundle.png" alt="All books bundle" loading="lazy" /></a></p>
<p align="center"><i>Poster created using <a href="https://www.canva.com/">Canva</a></i></p>
<ul>
<li><strong>All books bundle</strong>: <a href="https://leanpub.com/b/learnbyexample-all-books">leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/all-books">gumroad</a>
<ul>
<li>13 programming ebooks on Regular Expressions, Linux CLI tools, Python, Vim and more</li>
</ul>
</li>
<li><strong>Linux CLI Text Processing bundle</strong>: <a href="https://leanpub.com/b/linux-cli-text-processing">leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/linux-cli-text-processing">gumroad</a>
<ul>
<li>GNU grep, sed, awk, Perl and Ruby one-liners, GNU coreutils, CLI computing</li>
</ul>
</li>
<li><strong>Awesome regex</strong>: <a href="https://leanpub.com/b/regex">leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/regex">gumroad</a>
<ul>
<li>Python, Ruby, JavaScript Regular expressions</li>
<li>GNU grep, ripgrep, GNU sed, GNU awk CLI tools (BRE/ERE, PCRE, Rust regex crate, PCRE2)</li>
<li>Vim regexp</li>
</ul>
</li>
<li><strong>Magical one-liners</strong>: <a href="https://leanpub.com/b/oneliners">leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/oneliners">gumroad</a>
<ul>
<li>GNU grep, ripgrep, GNU sed, GNU awk, Ruby, Perl CLI tools</li>
</ul>
</li>
<li><strong>Learn by example Python bundle</strong>: <a href="https://leanpub.com/b/python-bundle">leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/python-bundle">gumroad</a>
<ul>
<li>Python introduction, Regular expressions and Projects</li>
</ul>
</li>
<li><strong>Ruby Text processing</strong>: <a href="https://leanpub.com/b/ruby-textprocessing">leanpub</a> or <a href="https://learnbyexample.gumroad.com/l/ruby-textprocessing">gumroad</a>
<ul>
<li>Ruby regular expressions, Ruby One-Liners Guide</li>
</ul>
</li>
</ul>
<br>
<h1 id="testimonials-heart-eyes"><p style="color: #c05b4d">Testimonials 😍</h1>
<blockquote>
<p>I love your books on regex...As a student from the Digital VLSI space, it is indeed useful now and definitely in the future. It's really well written and really easy to understand the examples.</p>
<p>— <a href="https://old.reddit.com/r/Python/comments/i0m2sy/i_know_python_basics_what_next/fzql5gh/">feedback on reddit</a></p>
</blockquote>
<blockquote>
<p>It's very thorough, written with care, and presented in a way that makes sense. Even as an intermediate Python programmer, I found use in this book.</p>
<p>— feedback by <a href="https://healeycodes.com/">Andrew Healey</a> on <a href="https://news.ycombinator.com/item?id=26082464">Hacker News</a> for "100 Page Python Intro"</p>
</blockquote>
<blockquote>
<p>Step up your cli fu with this fabulous intro & deep dive into awk. I learned a ton of tricks!</p>
<p>— <a href="https://twitter.com/killchain/status/1246820137455452163">feedback on twitter</a></p>
</blockquote>
<blockquote>
<p>Your Practice Python Projects book is really helping me to reinforce my knowledge and mastery of Python as I'm learning.</p>
<p>— <a href="https://twitter.com/tayporware/status/1446499855988400129">feedback on twitter</a></p>
</blockquote>
<blockquote>
<p>In my opinion the book does a great job of quickly presenting examples of how commands can be used and then paired up to achieve new or interesting ways of manipulating data. Throughout the text there are little highlights offering tips on extra functionality or limitations of certain commands. For instance, when discussing the <em>shuf</em> command we're warned that <em>shuf</em> will not work with multiple files. However, we can merge multiple files together (using the <em>cat</em> command) and then pass them to <em>shuf</em>. These little gems of wisdom add a dimension to the book and will likely save the reader some time wondering why their scripts are not working as expected.</p>
<p>— book review by Jesse Smith on <a href="https://distrowatch.com/weekly.php?issue=20211206#book">distrowatch.com</a> for "Command line text processing with GNU Coreutils"</p>
</blockquote>
<blockquote>
<p>Literally was having a mini-breakdown about not understanding Regex in algorithm solutions the other day and now I'm feeling so much better, so thank YOU! I genuinely feel like I'm developing the skill for spotting when and where to use them after so much practice!</p>
<p>— <a href="https://twitter.com/codingwithlucy/status/1450668315635036160">feedback on twitter</a></p>
</blockquote>
<blockquote>
<p>This Ruby one-liners cookbook is incredible. Pretty mind boggling all the stuff you can do.</p>
<p>— <a href="https://twitter.com/jbrancha/status/1506766118756786189">feedback on twitter</a></p>
</blockquote>
<blockquote>
<p>Hi, great work releasing this! Trying to explain vim concisely is always an interesting challenge and I had a great time reading your attempt in this book. I always find it really interesting on how people try to group certain vim functions in a way that makes sense to people that don't use vim. I think you cover that idea pretty well in your 'Vim philosophy and features' section whilst not making it overly abstract and keeping it relatable.</p>
<p>— <a href="https://news.ycombinator.com/item?id=30684232">feedback on Hacker News</a> by doix for "Vim Reference Guide"</p>
</blockquote>
<blockquote>
<p>I consider myself pretty experienced at shell-fu and capable of doing most things I set out to achieve in either bash scripts or fearless one-liners. However, my awk is rudimentary at best, I think mostly because it's such an unforgiving environment to experiment in. These books you've written are great for a bit of first principles insight and then quickly building up to functional usage. I will have no hesitation in referring colleagues to them!</p>
<p>— <a href="https://news.ycombinator.com/item?id=31930840">feedback on Hacker News</a></p>
</blockquote>
<blockquote>
<p>Thank you for choosing to write and share your knowledge. I read your books on CLI and sed - I think they are very comprehensive and very well explained. Keep up the great work</p>
<p>— <a href="https://twitter.com/le_anh_phuong/status/1628149732760604672">feedback on twitter</a></p>
</blockquote>
<blockquote>
<p>This is fantastic! 👏 I use Perl one-liners for record and text processing a lot and this will be definitely something I will keep coming back to - I’ve already learned a trick from “Context Matching” (9) 🙂</p>
<p>— <a href="https://programming.dev/comment/3277968">feedback on [email protected]</a></p>
</blockquote>
<blockquote>
<p>Nice book! I just started trying to get into linux today and you have some tips I haven’t found elsewhere and the text is an enjoyable read so far.</p>
<p>— <a href="https://old.reddit.com/r/linux4noobs/comments/1adrx6c/linux_guide_for_beginners/kk3dypr/">feedback on reddit</a></p>
</blockquote>
<blockquote>
<p>I discovered your books recently and they’re awesome, thank you! As a 20 year *nix they made me realize how much more there are to these rock solid and ancient tools, once you spend the time to actually learn the intricacies of them.</p>
<p>— <a href="https://old.reddit.com/r/commandline/comments/1byumd6/learn_gnu_coreutils_text_processing_tools_like/l2pk5bd/">feedback on reddit</a></p>
</blockquote>
<blockquote>
<p>I love the whole learn by example premise. Those exercises at the end are so valuable, as it often times leads me to find multiple solutions which helps me conceptualize how commands work with each other much better!</p>
<p>— <a href="https://old.reddit.com/r/linux4noobs/comments/lkbr65/learn_grep_sed_awk_perl_oneliners_with_hundreds/l6btf13/?context=3">feedback on reddit</a></p>
</blockquote>
<br>
<h1 id="100-page-python-intro"><p style="color: #ff9933">100 Page Python Intro</h1>
<p>Short, introductory guide for the Python programming language, suited for those already familiar with programming basics.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/100_page_python_intro/main/images/py_intro_ls.png" alt="100 Page Python Intro cover image" loading="lazy" /></p>
<ul>
<li><a href="https://github.com/learnbyexample/100_page_python_intro/blob/main/sample_chapters/100_page_python_intro_sample.pdf">Sample chapters</a></li>
<li>Buy pdf/epub from:
<ul>
<li><a href="https://leanpub.com/100pagepythonintro">leanpub</a></li>
<li><a href="https://learnbyexample.gumroad.com/l/100pagepythonintro">gumroad</a></li>
</ul>
</li>
<li><a href="https://github.com/learnbyexample/100_page_python_intro">GitHub repo for code snippets and more</a></li>
<li><a href="https://learnbyexample.github.io/100_page_python_intro/">web version</a></li>
<li>Feedback: <a href="https://twitter.com/learn_byexample">Twitter</a></li>
</ul>
<br>
<h1 id="understanding-python-re-gex"><p style="color: #ff9933">Understanding Python re(gex)?</h1>
<p>Learn Python Regular Expressions step-by-step from beginner to advanced levels with 300+ examples. Both <code>re</code> and <code>regex</code> modules are covered. Exercises are also included to test your understanding.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/py_regular_expressions/master/images/py_regex_ls.png" alt="Understanding Python re(gex)? cover image" loading="lazy" /></p>
<ul>
<li><a href="https://github.com/learnbyexample/py_regular_expressions/blob/master/sample_chapters/py_regex_sample.pdf">Sample chapters</a></li>
<li>Buy pdf/epub from:
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/py_regex">gumroad</a></li>
<li><a href="https://leanpub.com/py_regex">leanpub</a></li>
</ul>
</li>
<li><a href="https://github.com/learnbyexample/py_regular_expressions">GitHub repo for code snippets and more</a></li>
<li><a href="https://learnbyexample.github.io/py_regular_expressions/">web version</a></li>
<li>Feedback: <a href="https://twitter.com/learn_byexample">Twitter</a></li>
</ul>
<br>
<h1 id="practice-python-projects"><p style="color: #ff9933">Practice Python Projects</h1>
<p>Know Python basics but don't know what to do next? Take the next step in your programming journey with real world inspired Python projects.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/practice_python_projects/main/images/py_projects_ls.png" alt="Practice Python Projects cover image" loading="lazy" /></p>
<ul>
<li><a href="https://github.com/learnbyexample/practice_python_projects/blob/main/sample_chapters/practice_python_projects_sample.pdf">Sample chapters</a></li>
<li>Buy pdf/epub from:
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/py_projects">gumroad</a></li>
<li><a href="https://leanpub.com/py_projects">leanpub</a></li>
</ul>
</li>
<li><a href="https://github.com/learnbyexample/practice_python_projects">GitHub repo for code snippets and more</a></li>
<li><a href="https://learnbyexample.github.io/practice_python_projects/">web version</a></li>
<li>Feedback: <a href="https://twitter.com/learn_byexample">Twitter</a></li>
</ul>
<br>
<h1 id="understanding-javascript-regexp"><p style="color: #ff9933">Understanding JavaScript RegExp</h1>
<p>Learn JavaScript Regular Expressions step-by-step from beginner to advanced levels with hundreds of examples and exercises.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/learn_js_regexp/master/images/js_regexp_ls.png" alt="Understanding JavaScript RegExp cover image" loading="lazy" /></p>
<ul>
<li><a href="https://github.com/learnbyexample/learn_js_regexp/blob/master/sample_chapters/js_regexp_sample.pdf">Sample chapters</a></li>
<li>Buy pdf/epub from:
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/js_regexp">gumroad</a></li>
<li><a href="https://leanpub.com/js_regexp">leanpub</a></li>
</ul>
</li>
<li><a href="https://github.com/learnbyexample/learn_js_regexp">GitHub repo for code snippets and more</a></li>
<li><a href="https://learnbyexample.github.io/learn_js_regexp/">web version</a></li>
<li>Feedback: <a href="https://twitter.com/learn_byexample">Twitter</a></li>
</ul>
<br>
<h1 id="cli-text-processing-with-gnu-grep-and-ripgrep"><p style="color: #ff9933">CLI text processing with GNU grep and ripgrep</h1>
<p>Example based guide to mastering GNU grep and ripgrep. Exercises are also included to test your understanding.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/learn_gnugrep_ripgrep/master/images/grep_ls.png" alt="CLI text processing with GNU grep and ripgrep cover image" loading="lazy" /></p>
<ul>
<li><a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep/blob/master/sample_chapters/gnu_grep_sample.pdf">Sample chapters</a></li>
<li>Buy pdf/epub from:
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/gnugrep_ripgrep">gumroad</a></li>
<li><a href="https://leanpub.com/gnugrep_ripgrep">leanpub</a></li>
</ul>
</li>
<li><a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep">GitHub repo for code snippets and more</a></li>
<li><a href="https://learnbyexample.github.io/learn_gnugrep_ripgrep/">web version</a></li>
<li>Feedback: <a href="https://twitter.com/learn_byexample">Twitter</a></li>
</ul>
<br>
<h1 id="cli-text-processing-with-gnu-sed"><p style="color: #ff9933">CLI text processing with GNU sed</h1>
<p>Example based guide to mastering GNU sed. Exercises are also included to test your understanding.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/learn_gnused/master/images/sed_ls.png" alt="CLI text processing with GNU sed cover image" loading="lazy" /></p>
<ul>
<li><a href="https://github.com/learnbyexample/learn_gnused/blob/master/sample_chapters/gnu_sed_sample.pdf">Sample chapters</a></li>
<li>Buy pdf/epub from:
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/gnu_sed">gumroad</a></li>
<li><a href="https://leanpub.com/gnu_sed">leanpub</a></li>
</ul>
</li>
<li><a href="https://github.com/learnbyexample/learn_gnused">GitHub repo for code snippets and more</a></li>
<li><a href="https://learnbyexample.github.io/learn_gnused/">web version</a></li>
<li>Feedback: <a href="https://twitter.com/learn_byexample">Twitter</a></li>
</ul>
<br>
<h1 id="cli-text-processing-with-gnu-awk"><p style="color: #ff9933">CLI text processing with GNU awk</h1>
<p>Example based guide to mastering GNU awk one-liners. Exercises are also included to test your understanding.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/learn_gnuawk/master/images/gawk_ls.png" alt="CLI text processing with GNU awk cover image" loading="lazy" /></p>
<ul>
<li><a href="https://github.com/learnbyexample/learn_gnuawk/blob/master/sample_chapters/gnu_awk_sample.pdf">Sample chapters</a></li>
<li>Buy pdf/epub from:
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/gnu_awk">gumroad</a></li>
<li><a href="https://leanpub.com/gnu_awk">leanpub</a></li>
</ul>
</li>
<li><a href="https://github.com/learnbyexample/learn_gnuawk">GitHub repo for code snippets and more</a></li>
<li><a href="https://learnbyexample.github.io/learn_gnuawk/">web version</a></li>
<li>Feedback: <a href="https://twitter.com/learn_byexample">Twitter</a></li>
</ul>
<br>
<h1 id="understanding-ruby-regexp"><p style="color: #ff9933">Understanding Ruby Regexp</h1>
<p>Learn Ruby Regular Expressions step-by-step from beginner to advanced levels with hundreds of examples and exercises.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/Ruby_Regexp/master/images/ruby_regexp_ls.png" alt="Understanding Ruby Regexp cover image" loading="lazy" /></p>
<ul>
<li><a href="https://github.com/learnbyexample/Ruby_Regexp/blob/master/sample_chapters/ruby_regexp_sample.pdf">Sample chapters</a></li>
<li>Pay what you want for pdf/epub:
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/rubyregexp">gumroad</a></li>
<li><a href="https://leanpub.com/rubyregexp">leanpub</a></li>
</ul>
</li>
<li><a href="https://github.com/learnbyexample/Ruby_Regexp">GitHub repo for code snippets and more</a></li>
<li><a href="https://learnbyexample.github.io/Ruby_Regexp/">web version</a></li>
<li>Feedback: <a href="https://twitter.com/learn_byexample">Twitter</a></li>
</ul>
<br>
<h1 id="ruby-one-liners-guide"><p style="color: #ff9933">Ruby One-Liners Guide</h1>
<p>Example based guide for text processing with Ruby from the command line. Exercises are also included to test your understanding.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/learn_ruby_oneliners/master/images/ruby_oneliners_ls.png" alt="Ruby One-Liners Guide cover image" loading="lazy" /></p>
<ul>
<li><a href="https://github.com/learnbyexample/learn_ruby_oneliners/blob/master/sample_chapters/ruby_oneliners_sample.pdf">Sample chapters</a></li>
<li>Buy pdf/epub from:
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/ruby-oneliners">gumroad</a></li>
<li><a href="https://leanpub.com/ruby-oneliners">leanpub</a></li>
</ul>
</li>
<li><a href="https://github.com/learnbyexample/learn_ruby_oneliners">GitHub repo for code snippets and more</a></li>
<li><a href="https://learnbyexample.github.io/learn_ruby_oneliners/">web version</a></li>
<li>Feedback: <a href="https://twitter.com/learn_byexample">Twitter</a></li>
</ul>
<br>
<h1 id="perl-one-liners-guide"><p style="color: #ff9933">Perl One-Liners Guide</h1>
<p>Example based guide for text processing with Perl from the command line. Exercises are also included to test your understanding.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/learn_perl_oneliners/main/images/perl_oneliners_ls.png" alt="Perl One-Liners Guide cover image" loading="lazy" /></p>
<ul>
<li><a href="https://github.com/learnbyexample/learn_perl_oneliners/blob/main/sample_chapters/perl_oneliners_sample.pdf">Sample chapters</a></li>
<li>Buy pdf/epub from:
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/perl-oneliners">gumroad</a></li>
<li><a href="https://leanpub.com/perl-oneliners">leanpub</a></li>
</ul>
</li>
<li><a href="https://github.com/learnbyexample/learn_perl_oneliners">GitHub repo for code snippets and more</a></li>
<li><a href="https://learnbyexample.github.io/learn_perl_oneliners/">web version</a></li>
<li>Feedback: <a href="https://twitter.com/learn_byexample">Twitter</a></li>
</ul>
<br>
<h1 id="cli-text-processing-with-gnu-coreutils"><p style="color: #ff9933">CLI text processing with GNU Coreutils</h1>
<p>You might be already aware of popular coreutils commands like <code>head</code>, <code>tail</code>, <code>tr</code>, <code>sort</code> and so on. This book will teach you more than twenty of such specialized text processing tools provided by the <code>GNU coreutils</code> package.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/cli_text_processing_coreutils/main/images/cli_coreutils_ls.png" alt="CLI text processing with GNU Coreutils cover image" loading="lazy" /></p>
<ul>
<li><a href="https://github.com/learnbyexample/cli_text_processing_coreutils/blob/main/sample_chapters/cli_text_processing_coreutils_sample.pdf">Sample chapters</a></li>
<li>Buy pdf/epub from:
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/cli_coreutils">gumroad</a></li>
<li><a href="https://leanpub.com/cli_coreutils">leanpub</a></li>
</ul>
</li>
<li><a href="https://github.com/learnbyexample/cli_text_processing_coreutils">GitHub repo for code snippets and more</a></li>
<li><a href="https://learnbyexample.github.io/cli_text_processing_coreutils/">web version</a></li>
<li>Feedback: <a href="https://twitter.com/learn_byexample">Twitter</a></li>
</ul>
<br>
<h1 id="vim-reference-guide"><p style="color: #ff9933">Vim Reference Guide</h1>
<p>This is intended as a concise learning resource for beginner to intermediate level Vim users. It has more in common with cheatsheets than a typical text book. Topics like Regular Expressions and Macros have more detailed explanations and examples due to their complexity.</p>
<p align="center"><img src="https://learnbyexample.github.io/vim_reference/images/vim_reference_guide_ls.png" alt="Vim Reference Guide cover image" loading="lazy" /></p>
<ul>
<li><a href="https://github.com/learnbyexample/vim_reference/blob/master/sample_chapters/vim_reference_guide_sample.pdf">Sample chapters</a></li>
<li>Buy pdf/epub from:
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/vim_reference_guide">gumroad</a></li>
<li><a href="https://leanpub.com/vim_reference_guide">leanpub</a></li>
</ul>
</li>
<li><a href="https://github.com/learnbyexample/vim_reference">GitHub repo</a></li>
<li><a href="https://learnbyexample.github.io/vim_reference/">web version</a></li>
<li>Feedback: <a href="https://twitter.com/learn_byexample">Twitter</a></li>
</ul>
<br>
<h1 id="linux-command-line-computing"><p style="color: #ff9933">Linux Command Line Computing</h1>
<p>This ebook aims to teach <strong>Linux command line tools and Shell Scripting</strong> for <strong>beginner to intermediate</strong> level users. The main focus is towards <strong>managing your files</strong> and performing <strong>text processing tasks</strong>. Plenty of <strong>examples</strong> are provided to make it easier to understand a particular tool and its various features. <strong>Exercises</strong> at the end of chapters will help you practice what you've learned and <strong>solutions</strong> are provided for reference. I hope this ebook would make it much easier for you to discover CLI tools, features and learning resources than my own blundering experience.</p>
<p align="center"><img src="https://learnbyexample.github.io/cli-computing/images/cli_computing_ls.png" alt="Linux Command Line Computing cover image" loading="lazy" /></p>
<ul>
<li><a href="https://github.com/learnbyexample/cli-computing/blob/master/sample_chapters/cli_computing_sample.pdf">Sample chapters</a></li>
<li>Buy pdf/epub from:
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/cli_computing">gumroad</a></li>
<li><a href="https://leanpub.com/cli_computing">leanpub</a></li>
</ul>
</li>
<li><a href="https://github.com/learnbyexample/cli-computing">GitHub repo</a></li>
<li><a href="https://learnbyexample.github.io/cli-computing/">web version</a></li>
<li>Feedback: <a href="https://twitter.com/learn_byexample">Twitter</a></li>
</ul>
CLI tip 14: specify permissions during directory creation2022-08-09T00:00:00+00:002022-08-09T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-14/<p>You can use <code>mkdir -m</code> instead of creating a directory with <code>mkdir</code> first and then changing the directory permissions with the <code>chmod</code> command. The argument to the <code>-m</code> (mode) option uses the same syntax as the <code>chmod</code> command.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># instead of this
</span><span>$ mkdir back_up
</span><span>$ chmod </span><span style="color:#b3933a;">750</span><span> back_up
</span><span>
</span><span style="color:#7f8989;"># do this
</span><span>$ mkdir </span><span style="color:#72ab00;">-</span><span>m </span><span style="color:#b3933a;">750</span><span> back_up
</span><span>$ stat </span><span style="color:#72ab00;">-</span><span>c </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%a %A</span><span style="color:#d07711;">'</span><span> back_up
</span><span style="color:#b3933a;">750</span><span> drwxr</span><span style="color:#72ab00;">-</span><span>x</span><span style="color:#72ab00;">---
</span></code></pre>
<p>Here are some more examples:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ mkdir </span><span style="color:#72ab00;">-</span><span>m </span><span style="color:#72ab00;">=</span><span>rx dummy_dir
</span><span>$ stat </span><span style="color:#72ab00;">-</span><span>c </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%a %A</span><span style="color:#d07711;">'</span><span> dummy_dir
</span><span style="color:#b3933a;">555</span><span> dr</span><span style="color:#72ab00;">-</span><span>xr</span><span style="color:#72ab00;">-</span><span>xr</span><span style="color:#72ab00;">-</span><span>x
</span><span>
</span><span>$ mkdir </span><span style="color:#72ab00;">-</span><span>m go</span><span style="color:#72ab00;">-</span><span>rwx dot_files
</span><span>$ stat </span><span style="color:#72ab00;">-</span><span>c </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%a %A</span><span style="color:#d07711;">'</span><span> dot_files
</span><span style="color:#b3933a;">700</span><span> drwx</span><span style="color:#72ab00;">------
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/8PFWbzFue14" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/cli-computing">Linux Command Line Computing</a> ebook.</p>
Python tip 14: sequence unpacking2022-08-03T00:00:00+00:002022-08-08T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-14/<p>You can assign the individual elements of a sequence to multiple variables. This is known as <strong>sequence unpacking</strong> and it is handy in many situations.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>details </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'2018-10-25'</span><span>, </span><span style="color:#d07711;">'car'</span><span>, </span><span style="color:#b3933a;">2346</span><span>]
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>purchase_date, vehicle, qty </span><span style="color:#72ab00;">= </span><span>details
</span><span style="color:#72ab00;">>>> </span><span>purchase_date
</span><span style="color:#d07711;">'2018-10-25'
</span><span style="color:#72ab00;">>>> </span><span>vehicle
</span><span style="color:#d07711;">'car'
</span><span style="color:#72ab00;">>>> </span><span>qty
</span><span style="color:#b3933a;">2346
</span></code></pre>
<p>Here's how you can easily assign and swap multiple variables.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># multiple assignments
</span><span style="color:#72ab00;">>>> </span><span>num1, num2, num3 </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">3.14</span><span>, </span><span style="color:#b3933a;">42</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">100
</span><span>
</span><span style="color:#7f8989;"># swapping values
</span><span style="color:#72ab00;">>>> </span><span>num1, num2, num3 </span><span style="color:#72ab00;">= </span><span>num3, num1, num2
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">print</span><span>(</span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{num1 = }</span><span style="color:#aeb52b;">\n</span><span>{num2 = }</span><span style="color:#aeb52b;">\n</span><span>{num3 = }</span><span style="color:#d07711;">'</span><span>)
</span><span>num1 </span><span style="color:#72ab00;">= -</span><span style="color:#b3933a;">100
</span><span>num2 </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">3.14
</span><span>num3 </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">42
</span></code></pre>
<p>Unpacking isn't limited to mapping every element of the sequence. You can use a <code>*</code> prefix to catch all the remaining values (if any is left) in a <code>list</code> variable.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>values </span><span style="color:#72ab00;">= </span><span>(</span><span style="color:#d07711;">'first'</span><span>, </span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#b3933a;">200</span><span>, </span><span style="color:#b3933a;">300</span><span>, </span><span style="color:#d07711;">'last'</span><span>)
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>x, </span><span style="color:#72ab00;">*</span><span>y </span><span style="color:#72ab00;">= </span><span>values
</span><span style="color:#72ab00;">>>> </span><span>x
</span><span style="color:#d07711;">'first'
</span><span style="color:#72ab00;">>>> </span><span>y
</span><span>[</span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#b3933a;">200</span><span>, </span><span style="color:#b3933a;">300</span><span>, </span><span style="color:#d07711;">'last'</span><span>]
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>s1, </span><span style="color:#72ab00;">*</span><span>nums, s2 </span><span style="color:#72ab00;">= </span><span>values
</span><span style="color:#72ab00;">>>> </span><span>s1
</span><span style="color:#d07711;">'first'
</span><span style="color:#72ab00;">>>> </span><span>nums
</span><span>[</span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#b3933a;">200</span><span>, </span><span style="color:#b3933a;">300</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>s2
</span><span style="color:#d07711;">'last'
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://mathspp.com/blog/pydonts/unpacking-with-starred-assignments">Unpacking with starred assignments</a> for more examples and explanations.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/0hOl2YSkO4w" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 12: save and restore sessions2022-07-26T00:00:00+00:002022-07-26T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-12/<p>You can save and restore Vim sessions to continue working with the same setup before you had to quit Vim for reasons like switching off the machine, switching to another project, etc.</p>
<ul>
<li><kbd>:mksession proj.vim</kbd> save the current Vim session with details like cursor position, file list, layout, etc
<ul>
<li>you can customize things to be saved using the <code>sessionoptions</code> setting</li>
<li>for example, <kbd>:set sessionoptions+=resize</kbd> will save resized window information as well</li>
</ul>
</li>
<li><kbd>:mksession! proj.vim</kbd> overwrite existing session</li>
<li><kbd>:source proj.vim</kbd> restore Vim session from <code>proj.vim</code> file
<ul>
<li><code>vim -S proj.vim</code> restore a session from the command line when launching Vim</li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/usr_21.txt.html#21.4">:h 21.4</a>, <a href="https://vimhelp.org/starting.txt.html#views-sessions">:h views-sessions</a> and <a href="https://vimhelp.org/options.txt.html#%27sessionoptions%27">:h 'sessionoptions'</a> for more details.</p>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://stackoverflow.com/q/1642611/4082052">stackoverflow: How to save and restore multiple different sessions in Vim?</a> for custom settings to automate the save and restore process and other tips and tricks. See also <a href="https://github.com/iggredible/Learn-Vim/blob/master/ch20_views_sessions_viminfo.md">Learn-Vim: Views, Sessions, and Viminfo</a>.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/8FERF0M2Dm4" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 13: join lines of two files based on the first field2022-07-20T00:00:00+00:002022-07-20T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-13/<p>By default, <code>join</code> combines two files based on the first field content (also referred as <strong>key</strong>). Only the lines with common keys will be part of the output. The key field will be displayed first in the output (this distinction will come into play if the first field isn't the key). Rest of the line will have the remaining fields from the first and second files, in that order. One or more blanks (space or tab) will be considered as the input field separator and a single space will be used as the output field separator. If present, blank characters at the start of the input lines will be ignored.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># sample sorted input files
</span><span>$ cat jan.txt
</span><span>apple </span><span style="color:#b3933a;">10
</span><span>banana </span><span style="color:#b3933a;">20
</span><span>soap </span><span style="color:#b3933a;">3
</span><span>tshirt </span><span style="color:#b3933a;">3
</span><span>$ cat feb.txt
</span><span>banana </span><span style="color:#b3933a;">15
</span><span>fig </span><span style="color:#b3933a;">100
</span><span>pen </span><span style="color:#b3933a;">2
</span><span>soap </span><span style="color:#b3933a;">1
</span><span>
</span><span style="color:#7f8989;"># combine common lines based on the first field
</span><span>$ join jan.txt feb.txt
</span><span>banana </span><span style="color:#b3933a;">20 15
</span><span>soap </span><span style="color:#b3933a;">3 1
</span></code></pre>
<p>Here's an <code>awk</code> version to do the same. Helpful if you want to do some additional processing that won't be possible with the <code>join</code> command. Another advantage is that this solution will work even if the input files are not sorted.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ awk </span><span style="color:#d07711;">'NR==FNR{a[$1]=$2; next} $1 in a{print $1, a[$1], $2}'</span><span> jan.txt feb.txt
</span><span>banana </span><span style="color:#b3933a;">20 15
</span><span>soap </span><span style="color:#b3933a;">3 1
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/Pjku4w7J6Zg" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://learnbyexample.github.io/cli_text_processing_coreutils/join.html">join chapter</a> from my <a href="https://github.com/learnbyexample/cli_text_processing_coreutils">Command line text processing with GNU Coreutils</a> ebook for more details and examples.</p>
Python tip 13: formatting numbers with underscore separation2022-07-13T00:00:00+00:002022-07-13T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-13/<p>For readability purposes, you can use underscores while declaring large numbers. For example:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span style="color:#b3933a;">1_000_000_000
</span><span style="color:#b3933a;">1000000000
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#b3933a;">0b1000_1111
</span><span style="color:#b3933a;">143
</span></code></pre>
<p>Did you know that you can also format numbers with underscore separation?</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>n </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">14310023
</span><span>
</span><span style="color:#7f8989;"># underscore separation
</span><span style="color:#72ab00;">>>> </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{n</span><span style="color:#b3933a;">:_</span><span>}</span><span style="color:#d07711;">'
</span><span style="color:#d07711;">'14_310_023'
</span><span>
</span><span style="color:#7f8989;"># you can also use comma separation for integers
</span><span style="color:#72ab00;">>>> </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{n</span><span style="color:#b3933a;">:,</span><span>}</span><span style="color:#d07711;">'
</span><span style="color:#d07711;">'14,310,023'
</span></code></pre>
<p>Here are some examples for displaying numbers in binary, octal and hexadecimal formats:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>n </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">14310023
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{n</span><span style="color:#b3933a;">:_b</span><span>}</span><span style="color:#d07711;">'
</span><span style="color:#d07711;">'1101_1010_0101_1010_1000_0111'
</span><span style="color:#72ab00;">>>> </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{n</span><span style="color:#b3933a;">:#_b</span><span>}</span><span style="color:#d07711;">'
</span><span style="color:#d07711;">'0b1101_1010_0101_1010_1000_0111'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{n</span><span style="color:#b3933a;">:#_x</span><span>}</span><span style="color:#d07711;">'
</span><span style="color:#d07711;">'0xda_5a87'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{n</span><span style="color:#b3933a;">:#_o</span><span>}</span><span style="color:#d07711;">'
</span><span style="color:#d07711;">'0o6645_5207'
</span></code></pre>
<p>And here's an example with zero filling:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> for </span><span>n </span><span style="color:#72ab00;">in </span><span>(</span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">20</span><span>, </span><span style="color:#b3933a;">28</span><span>):
</span><span style="color:#b3933a;">... </span><span style="color:#b39f04;">print</span><span>(</span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{n</span><span style="color:#b3933a;">:09_b</span><span>}</span><span style="color:#d07711;">'</span><span>)
</span><span style="color:#b3933a;">...
</span><span style="color:#b3933a;">0000</span><span>_0011
</span><span style="color:#b3933a;">0001</span><span>_0100
</span><span style="color:#b3933a;">0001</span><span>_1100
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://docs.python.org/3/reference/lexical_analysis.html#f-strings">docs.python: Formatted string literals</a> for documentation and other examples.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/wFg0wS8nY4o" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 11: replace characters in Normal mode2022-07-06T00:00:00+00:002022-07-06T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-11/<p>Often, you just need to change one character. For example, changing <code>i</code> to <code>j</code>, <code>2</code> to <code>4</code>, <code>'</code> to <code>"</code> and so on.</p>
<ul>
<li><kbd>rj</kbd> replace the character under the cursor with <code>j</code></li>
<li><kbd>ry</kbd> replace the character under the cursor with <code>y</code></li>
<li><kbd>3ra</kbd> replace the character under cursor as well as the two characters to the right with <code>aaa</code>
<ul>
<li>no changes will be made if there aren't sufficient characters to match</li>
</ul>
</li>
</ul>
<p>To replace multiple characters with different characters, use <code>R</code>.</p>
<ul>
<li><kbd>Rlion</kbd> followed by <kbd>Esc</kbd> replace the character under cursor and three characters to the right with <code>lion</code>
<ul>
<li><kbd>Esc</kbd> key marks the completion of <code>R</code> command</li>
<li><kbd>Backspace</kbd> key will act as an undo command to give back the character that was replaced</li>
<li>if you are replacing at the end of a line, the line will be automatically extended if needed</li>
</ul>
</li>
</ul>
<p>The advantage of <code>r</code> and <code>R</code> commands is that you remain in the Normal mode, without needing to switch to Insert mode and back.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/cNRNiwIcqNc" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 12: squeeze empty lines2022-06-29T00:00:00+00:002022-06-29T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-12/<p><code>awk</code> has a builtin feature to process input content paragraph wise (by setting <code>RS</code> to an empty string). But, did you know that <code>cat</code>, <code>less</code> and <code>grep</code> can also be used to squeeze empty lines?</p>
<p><code>cat -s</code> (and <code>less -s</code>) will squeeze multiple empty lines in the input to a single empty line in the output. Here's an example:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat ip.txt
</span><span>hello
</span><span>
</span><span>
</span><span>
</span><span>
</span><span>world
</span><span>
</span><span>apple
</span><span>banana
</span><span>cherry
</span><span>
</span><span>
</span><span>tea coffee
</span><span>chocolate
</span><span>$ cat </span><span style="color:#72ab00;">-</span><span>s ip.txt
</span><span>hello
</span><span>
</span><span>world
</span><span>
</span><span>apple
</span><span>banana
</span><span>cherry
</span><span>
</span><span>tea coffee
</span><span>chocolate
</span></code></pre>
<p>Here's an example with empty lines at the start/end of the input:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'\n\n\ndragon\n\n\nunicorn\n\n\n'
</span><span>
</span><span>
</span><span>
</span><span>dragon
</span><span>
</span><span>
</span><span>unicorn
</span><span>
</span><span>
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'\n\n\ndragon\n\n\nunicorn\n\n\n' </span><span style="color:#72ab00;">|</span><span> cat </span><span style="color:#72ab00;">-</span><span>s
</span><span>
</span><span>dragon
</span><span>
</span><span>unicorn
</span><span>
</span></code></pre>
<p>And here's a solution with <code>awk</code>. Unlike the <code>-s</code> option, this will completely remove empty lines at the start/end of the input.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ awk </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">RS</span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'{print s $0; s="\n"}'</span><span> ip.txt
</span><span>hello
</span><span>
</span><span>world
</span><span>
</span><span>apple
</span><span>banana
</span><span>cherry
</span><span>
</span><span>tea coffee
</span><span>chocolate
</span><span>
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'\n\n\ndragon\n\n\nunicorn\n\n\n' </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">RS</span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'{print s $0; s="\n"}'
</span><span>dragon
</span><span>
</span><span>unicorn
</span></code></pre>
<p>The <code>awk</code> solution would be easier to extend, given its programmable features. For example, two empty lines between the groups:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ awk </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">RS</span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'{print s $0; s="\n\n"}'</span><span> ip.txt
</span><span>hello
</span><span>
</span><span>
</span><span>world
</span><span>
</span><span>
</span><span>apple
</span><span>banana
</span><span>cherry
</span><span>
</span><span>
</span><span>tea coffee
</span><span>chocolate
</span></code></pre>
<p>And here's a surprising <code>GNU grep</code> solution, with a customizable group separator:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># single empty line
</span><span>$ grep </span><span style="color:#72ab00;">--</span><span>group</span><span style="color:#72ab00;">-</span><span>separator= </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">A0 </span><span style="color:#d07711;">'.'</span><span> ip.txt
</span><span>hello
</span><span>
</span><span>world
</span><span>
</span><span>apple
</span><span>banana
</span><span>cherry
</span><span>
</span><span>tea coffee
</span><span>chocolate
</span><span>
</span><span style="color:#7f8989;"># double empty line
</span><span style="color:#7f8989;"># empty lines at the start/end of the input are removed too
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'\n\n\ndragon\n\n\nunicorn\n\n\n' </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">--</span><span>group</span><span style="color:#72ab00;">-</span><span>separator=</span><span style="color:#5597d6;">$'</span><span>\n</span><span style="color:#d07711;">' -A0 '</span><span>.</span><span style="color:#d07711;">'
</span><span style="color:#d07711;">dragon
</span><span style="color:#d07711;">
</span><span style="color:#d07711;">
</span><span style="color:#d07711;">unicorn
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/DjNF2Sbwyxk" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/cli_text_processing_coreutils">CLI text processing with GNU Coreutils</a>, <a href="https://github.com/learnbyexample/learn_gnuawk">CLI text processing with GNU awk</a> and <a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep">CLI text processing with GNU grep and ripgrep</a> ebooks.</p>
Python tip 12: negate a regex grouping2022-06-22T00:00:00+00:002022-06-22T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-12/<p>You might be familiar with negating a character class, for example:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> import </span><span>re
</span><span>
</span><span style="color:#7f8989;"># remove first two columns
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">sub</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\A</span><span style="color:#7c8f4c;">(</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">:]</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">:)</span><span style="color:#72ab00;">{2}</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">''</span><span>, </span><span style="color:#d07711;">'apple:42:banana:1000:cherry:512'</span><span>)
</span><span style="color:#d07711;">'banana:1000:cherry:512'
</span><span>
</span><span style="color:#7f8989;"># filter all elements not ending with `r` or `t`
</span><span style="color:#72ab00;">>>> </span><span>words </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'surrender'</span><span>, </span><span style="color:#d07711;">'unicorn'</span><span>, </span><span style="color:#d07711;">'newer'</span><span>, </span><span style="color:#d07711;">'door'</span><span>, </span><span style="color:#d07711;">'empty'</span><span>, </span><span style="color:#d07711;">'eel'</span><span>, </span><span style="color:#d07711;">'pest'</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>[w </span><span style="color:#72ab00;">for </span><span>w </span><span style="color:#72ab00;">in </span><span>words </span><span style="color:#72ab00;">if </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">rt]</span><span style="color:#72ab00;">\Z</span><span style="color:#d07711;">'</span><span>, w)]
</span><span>[</span><span style="color:#d07711;">'unicorn'</span><span>, </span><span style="color:#d07711;">'empty'</span><span>, </span><span style="color:#d07711;">'eel'</span><span>]
</span></code></pre>
<p>But do you know how to match characters based on a negated group? You can use a combination of negative lookahead and quantifiers as shown in the examples below:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>pets </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'fox,cat,dog,parrot'
</span><span>
</span><span style="color:#7f8989;"># match if 'do' is not present between 'at' and 'par'
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">at((</span><span style="color:#aeb52b;">?!</span><span style="color:#7c8f4c;">do)</span><span style="color:#aeb52b;">.</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">par</span><span style="color:#d07711;">'</span><span>, pets))
</span><span style="color:#b3933a;">False
</span><span>
</span><span style="color:#7f8989;"># match if 'go' is not present between 'at' and 'par'
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">at((</span><span style="color:#aeb52b;">?!</span><span style="color:#7c8f4c;">go)</span><span style="color:#aeb52b;">.</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">par</span><span style="color:#d07711;">'</span><span>, pets))
</span><span style="color:#b3933a;">True
</span><span>
</span><span style="color:#7f8989;"># easier to understand by looking at the matched portions
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">at((</span><span style="color:#aeb52b;">?!</span><span style="color:#7c8f4c;">go)</span><span style="color:#aeb52b;">.</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">par</span><span style="color:#d07711;">'</span><span>, pets)[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#d07711;">'at,dog,par'
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\A</span><span style="color:#7c8f4c;">((</span><span style="color:#aeb52b;">?!</span><span style="color:#7c8f4c;">par)</span><span style="color:#aeb52b;">.</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">*</span><span style="color:#d07711;">'</span><span>, pets)[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#d07711;">'fox,cat,dog,'
</span></code></pre>
<p>The <code>.</code> in <code>((?!go).)*</code> will match a character only if the sequence of current and next characters are not <code>go</code>. Similarly, the <code>.</code> in <code>((?!par).)*</code> matches a character only if the current and next two characters are not <code>par</code>. The <code>*</code> quantifier is applied on the outer group to match zero or more characters satisfying the given condition.</p>
<p>The outer group in the above examples are capturing groups, though it wasn't required. Just makes the pattern concise. However, capturing groups affect the behavior of functions like <code>re.split</code> and <code>re.findall</code>. You can use non-capturing groups in such cases:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># capture group affects the behavior of 're.findall'
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\b</span><span style="color:#7c8f4c;">((</span><span style="color:#aeb52b;">?!</span><span style="color:#7c8f4c;">42)</span><span style="color:#aeb52b;">\w</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">+\b</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'a422b good bad42 nice100'</span><span>)
</span><span>[</span><span style="color:#d07711;">'d'</span><span>, </span><span style="color:#d07711;">'0'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># so, use a non-capturing group here
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#72ab00;">\b</span><span style="color:#7c8f4c;">(?:(</span><span style="color:#aeb52b;">?!</span><span style="color:#7c8f4c;">42)</span><span style="color:#aeb52b;">\w</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">+\b</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">'a422b good bad42 nice100'</span><span>)
</span><span>[</span><span style="color:#d07711;">'good'</span><span>, </span><span style="color:#d07711;">'nice100'</span><span>]
</span></code></pre>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> Test your understanding by solving this exercise. Construct a regex solution that works for all three sample transformations shown below:</p>
<ul>
<li><code>Power(x,2)</code> should be replaced with <code>(x)*(x)</code></li>
<li><code>Power(Power(x,2) + x,2)</code> should be changed to <code>((x)*(x) + x)*((x)*(x) + x)</code></li>
<li><code>Power(x + Power(x,2),2)</code> should be changed to <code>(x + (x)*(x))*(x + (x)*(x))</code></li>
</ul>
<p>If that was easy, make it work for general powers instead of just <code>2</code>:</p>
<ul>
<li><code>Power(Power(x,2),3)</code> translates to <code>((x)*(x))*((x)*(x))*((x)*(x))</code></li>
</ul>
<p>The above exercise is based on <a href="https://stackoverflow.com/q/67214116/4082052">this stackoverflow Q&A</a>.</p>
</blockquote>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/bkPS1dWZ2xU" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> ebook.</p>
Vim tip 10: Undo and Redo2022-06-15T00:00:00+00:002022-06-15T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-10/<p>In Normal mode, you can undo and redo changes using the following commands:</p>
<ul>
<li><kbd>u</kbd> undo last change
<ul>
<li>press <kbd>u</kbd> again for further undos</li>
</ul>
</li>
<li><kbd>U</kbd> undo latest changes on last edited line</li>
<li><kbd>Ctrl</kbd>+<kbd>r</kbd> redo a change undone by <kbd>u</kbd></li>
<li><kbd>U</kbd> redo changes undone by <kbd>U</kbd></li>
</ul>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/usr_32.txt.html#32.3">:h 32.3</a> for details on <kbd>g-</kbd> and <kbd>g+</kbd> commands that you can use to undo branches.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/ScTAZ0f6e-Q" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 11: longest line length2022-06-08T00:00:00+00:002022-06-08T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-11/<p>You can use <code>wc -L</code> to report the length of the longest line in the input (excluding the newline character of a line).</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ echo </span><span style="color:#d07711;">'apple' </span><span style="color:#72ab00;">|</span><span> wc </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">L
</span><span style="color:#b3933a;">5
</span><span>
</span><span style="color:#7f8989;"># last line not ending with newline won't be a problem
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'apple\nbanana' </span><span style="color:#72ab00;">|</span><span> wc </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">L
</span><span style="color:#b3933a;">6
</span><span>
</span><span>$ cat greeting.txt
</span><span>hi there
</span><span>have a nice day
</span><span>$ wc </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">L </span><span style="color:#72ab00;"><</span><span>greeting.txt
</span><span style="color:#b3933a;">15
</span></code></pre>
<p>If multiple files are passed, the last line summary will show the maximum length among the given inputs.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ wc </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">L</span><span> greeting.txt sample.txt para.txt
</span><span> </span><span style="color:#b3933a;">15</span><span> greeting.txt
</span><span> </span><span style="color:#b3933a;">26</span><span> sample.txt
</span><span> </span><span style="color:#b3933a;">11</span><span> para.txt
</span><span> </span><span style="color:#b3933a;">26</span><span> total
</span></code></pre>
<p><code>-L</code> won't count non-printable characters and tabs are converted to equivalent spaces. You can use <code>awk</code> if these are not acceptable.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># tab characters can occupy up to 8 columns
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'\t' </span><span style="color:#72ab00;">|</span><span> wc </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">L
</span><span style="color:#b3933a;">8
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'(\t)' </span><span style="color:#72ab00;">|</span><span> wc </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">L
</span><span style="color:#b3933a;">9
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'(\t)' </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#d07711;">'{print length()}'
</span><span style="color:#b3933a;">3
</span><span>
</span><span style="color:#7f8989;"># non-printable characters aren't counted
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'(\34)' </span><span style="color:#72ab00;">|</span><span> wc </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">L
</span><span style="color:#b3933a;">2
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'(\34)' </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#d07711;">'{print length()}'
</span><span style="color:#b3933a;">3
</span></code></pre>
<p>Note that the <code>awk</code> command in the above illustration is similar to <code>wc -L</code> only for single line inputs. For multiple lines, you can use the following command:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>awk </span><span style="color:#d07711;">'{len = length(); if(len > max) max = len} END{print max}'
</span></code></pre>
<p>Multibyte characters and <a href="https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries">grapheme clusters</a> will each be counted as <code>1</code>, assuming the current locale is set appropriately:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># multibyte characters are counted as 1 each in supported locales
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'αλεπού' </span><span style="color:#72ab00;">|</span><span> wc </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">L
</span><span style="color:#b3933a;">6
</span><span>
</span><span style="color:#7f8989;"># grapheme cluster example
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'cag̈e' </span><span style="color:#72ab00;">|</span><span> wc </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">L
</span><span style="color:#b3933a;">4
</span><span>
</span><span style="color:#7f8989;"># non-supported locales can cause them to be treated as non-printable
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'αλεπού' </span><span style="color:#72ab00;">| </span><span style="color:#c23f31;">LC_ALL</span><span style="color:#72ab00;">=</span><span style="color:#5597d6;">C</span><span> wc </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">L
</span><span style="color:#b3933a;">0
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/a6Mk0d0bqWU" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/cli_text_processing_coreutils">Command line text processing with GNU Coreutils</a> ebook.</p>
Bash compound commands and redirection2022-06-04T00:00:00+00:002022-06-04T00:00:00+00:00https://learnbyexample.github.io/mini/bash-compound-commands-redirection/<p>I've been using Linux for about 15 years. There are a lot of features I don't know and some that I've used but not often enough or to the full extent of possibilities.</p>
<p>Recently, I had written a <code>bash</code> function, which required saving the output of a <code>for</code> loop to a file. I knew that <a href="https://www.gnu.org/software/bash/manual/bash.html#Compound-Commands">compound commands</a> support <a href="https://www.gnu.org/software/bash/manual/bash.html#Redirections">redirection</a>, but it didn't strike me at that time as I haven't had to use them often.</p>
<p>Here's a simplified version of the function I wrote first:</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#c23f31;">pf</span><span>()
</span><span>{
</span><span> </span><span style="color:#72ab00;">></span><span> input.txt
</span><span> </span><span style="color:#72ab00;">for</span><span> f </span><span style="color:#72ab00;">in </span><span style="color:#d07711;">"$</span><span style="color:#acb3c2;">@</span><span style="color:#d07711;">" </span><span style="color:#72ab00;">; do </span><span style="color:#b39f04;">echo </span><span style="color:#d07711;">"$</span><span style="color:#acb3c2;">f </span><span style="color:#d07711;">$</span><span style="color:#acb3c2;">f</span><span style="color:#d07711;">.bkp" </span><span style="color:#72ab00;">>></span><span> input.txt </span><span style="color:#72ab00;">; done
</span><span> </span><span style="color:#5597d6;">cmd</span><span> input.txt </span><span style="color:#72ab00;">></span><span> output.txt
</span><span>}
</span></code></pre>
<p>Having to empty the file using <code>> input.txt</code> got me thinking that perhaps I was missing some obvious solution. Few days later, I realized that instead of using <code>>></code> during every iteration of the loop, I should have just applied <code>></code> to the loop itself.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#c23f31;">pf</span><span>()
</span><span>{
</span><span> </span><span style="color:#72ab00;">for</span><span> f </span><span style="color:#72ab00;">in </span><span style="color:#d07711;">"$</span><span style="color:#acb3c2;">@</span><span style="color:#d07711;">" </span><span style="color:#72ab00;">; do </span><span style="color:#b39f04;">echo </span><span style="color:#d07711;">"$</span><span style="color:#acb3c2;">f </span><span style="color:#d07711;">$</span><span style="color:#acb3c2;">f</span><span style="color:#d07711;">.bkp" </span><span style="color:#72ab00;">; done ></span><span> input.txt
</span><span> </span><span style="color:#5597d6;">cmd</span><span> input.txt </span><span style="color:#72ab00;">></span><span> output.txt
</span><span>}
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> <code>echo</code> and <code>cmd</code> in the above examples are just placeholders for illustration purposes. I needed both <code>input.txt</code> and <code>output.txt</code> after calling the function, which is why I didn't use <code>|</code> or process substitution.</p>
Python tip 11: capture external command output2022-06-01T00:00:00+00:002022-06-01T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-11/<p>The <code>subprocess</code> module provides plethora of features to execute external commands, capturing output being one of them. There are two ways to do so:</p>
<ul>
<li>passing <code>capture_output=True</code> to <code>subprocess.run()</code></li>
<li><code>subprocess.check_output()</code> if you only want <code>stdout</code></li>
</ul>
<p>By default, results are provided as <code>bytes</code> data type. You can change that by passing <code>text=True</code>.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> import </span><span>subprocess
</span><span style="color:#72ab00;">>>> </span><span>cmd </span><span style="color:#72ab00;">= </span><span>(</span><span style="color:#d07711;">'date'</span><span>, </span><span style="color:#d07711;">'-u'</span><span>, </span><span style="color:#d07711;">'+</span><span style="color:#aeb52b;">%A</span><span style="color:#d07711;">'</span><span>)
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>p </span><span style="color:#72ab00;">= </span><span>subprocess.</span><span style="color:#5597d6;">run</span><span>(cmd, </span><span style="color:#5597d6;">capture_output</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">True</span><span>, </span><span style="color:#5597d6;">text</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">True</span><span>)
</span><span style="color:#72ab00;">>>> </span><span>p
</span><span style="color:#5597d6;">CompletedProcess</span><span>(</span><span style="color:#5597d6;">args</span><span style="color:#72ab00;">=</span><span>(</span><span style="color:#d07711;">'date'</span><span>, </span><span style="color:#d07711;">'-u'</span><span>, </span><span style="color:#d07711;">'+</span><span style="color:#aeb52b;">%A</span><span style="color:#d07711;">'</span><span>), </span><span style="color:#5597d6;">returncode</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">0</span><span>,
</span><span> </span><span style="color:#5597d6;">stdout</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'Wednesday</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#5597d6;">stderr</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">''</span><span>)
</span><span style="color:#72ab00;">>>> </span><span>p.stdout
</span><span style="color:#d07711;">'Wednesday</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>subprocess.</span><span style="color:#5597d6;">check_output</span><span>(cmd, </span><span style="color:#5597d6;">text</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">True</span><span>)
</span><span style="color:#d07711;">'Wednesday</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">'
</span></code></pre>
<p>With <code>check_output()</code>, you'll get an exception if something goes wrong with the command being executed. With <code>run()</code>, you'll get that information from <code>stderr</code> and <code>returncode</code> as part of the <code>CompletedProcess</code> object.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>cmd </span><span style="color:#72ab00;">= </span><span>(</span><span style="color:#d07711;">'ls'</span><span>, </span><span style="color:#d07711;">'xyz.txt'</span><span>)
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>subprocess.</span><span style="color:#5597d6;">run</span><span>(cmd, </span><span style="color:#5597d6;">capture_output</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">True</span><span>, </span><span style="color:#5597d6;">text</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">True</span><span>)
</span><span style="color:#5597d6;">CompletedProcess</span><span>(</span><span style="color:#5597d6;">args</span><span style="color:#72ab00;">=</span><span>(</span><span style="color:#d07711;">'ls'</span><span>, </span><span style="color:#d07711;">'xyz.txt'</span><span>), </span><span style="color:#5597d6;">returncode</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#5597d6;">stdout</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">''</span><span>,
</span><span> </span><span style="color:#5597d6;">stderr</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">"ls: cannot access 'xyz.txt': No such file or directory</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">"</span><span>)
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>subprocess.</span><span style="color:#5597d6;">check_output</span><span>(cmd, </span><span style="color:#5597d6;">text</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">True</span><span>)
</span><span>ls: cannot access </span><span style="color:#d07711;">'xyz.txt'</span><span>: No such file </span><span style="color:#72ab00;">or </span><span>directory
</span><span style="color:#5597d6;">Traceback </span><span>(most recent call last):
</span><span> File </span><span style="color:#d07711;">"<stdin>"</span><span>, line </span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#72ab00;">in <</span><span>module</span><span style="color:#72ab00;">>
</span><span> File </span><span style="color:#d07711;">"/usr/lib/python3.8/subprocess.py"</span><span>, line </span><span style="color:#b3933a;">415</span><span>, </span><span style="color:#72ab00;">in </span><span>check_output
</span><span> </span><span style="color:#72ab00;">return </span><span style="color:#5597d6;">run</span><span>(</span><span style="color:#72ab00;">*</span><span>popenargs, </span><span style="color:#5597d6;">stdout</span><span style="color:#72ab00;">=</span><span style="color:#5597d6;">PIPE</span><span>, </span><span style="color:#5597d6;">timeout</span><span style="color:#72ab00;">=</span><span>timeout, </span><span style="color:#5597d6;">check</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">True</span><span>,
</span><span> File </span><span style="color:#d07711;">"/usr/lib/python3.8/subprocess.py"</span><span>, line </span><span style="color:#b3933a;">516</span><span>, </span><span style="color:#72ab00;">in </span><span>run
</span><span> </span><span style="background-color:#562d56bf;color:#f8f8f8;">raise</span><span> </span><span style="color:#5597d6;">CalledProcessError</span><span>(retcode, process.args,
</span><span>subprocess.CalledProcessError: Command </span><span style="color:#d07711;">'('</span><span>ls</span><span style="color:#d07711;">', '</span><span>xyz.txt</span><span style="color:#d07711;">')' </span><span>returned
</span><span> non</span><span style="color:#72ab00;">-</span><span>zero exit status </span><span style="color:#b3933a;">2.
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> You can also use legacy methods <code>subprocess.getstatusoutput()</code> and <code>subprocess.getoutput()</code> but they lack in features and do not provide secure options. See <a href="https://docs.python.org/3/library/subprocess.html#legacy-shell-invocation-functions">docs.python: subprocess Legacy Shell Invocation Functions</a> for details.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/uffQilOq9PA" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 9: named registers2022-05-24T00:00:00+00:002022-05-24T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-9/<p>In Normal mode, you can use lowercase alphabets <code>a-z</code> to save some content for future use. You can also append some more content to those registers by using the corresponding uppercase alphabets <code>A-Z</code> at a later stage.</p>
<ul>
<li><kbd>"ayy</kbd> copy the current line to the <code>"a</code> register</li>
<li><kbd>"bdip</kbd> delete the current paragraph, contents will also be saved to the <code>"b</code> register</li>
<li><kbd>"Ayj</kbd> append the current line and the line below to the <code>"a</code> register
<ul>
<li><kbd>"ayy</kbd> followed by <kbd>"Ayj</kbd> will result in total three lines in the <code>"a</code> register</li>
</ul>
</li>
<li><kbd>"ap</kbd> paste content from the <code>"a</code> register</li>
<li><kbd>"eyiw</kbd> copy word under the cursor to the <code>"e</code> register</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> You can use <kbd>:reg</kbd> (short for <code>:registers</code>) to view the contents of the registers. Specifying one or more characters (next to each other as a single string) will display contents only for those registers.</p>
<p><img src="/images/info.svg" alt="info" /> The named registers are also used for saving macros. You can record an empty macro to clear the contents, for example <kbd>qbq</kbd> clears the <code>"b</code> register.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/GBOtpKFcZSA" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
Debug woes 3: matching uppercase letters2022-05-13T00:00:00+00:002022-05-13T00:00:00+00:00https://learnbyexample.github.io/mini/debug-woes-3/<p>So, I was going through <a href="https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion">GNU bash manual: Shell Parameter Expansion</a> and trying out examples to check if I was understanding the features well.</p>
<p>When it came to case conversion, it was a bit confusing to know that you can only use a single character length glob. Here are the examples for lowercase to uppercase conversion that I used:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ fruit=</span><span style="color:#d07711;">'apple'
</span><span>
</span><span style="color:#7f8989;"># all characters to uppercase
</span><span>$ echo </span><span style="color:#d07711;">"${fruit^^}"
</span><span style="color:#5597d6;">APPLE
</span><span>
</span><span style="color:#7f8989;"># convert any character that matches [g-z] to uppercase
</span><span>$ echo </span><span style="color:#d07711;">"${fruit^^[g-z]}"
</span><span>aPPLe
</span><span>
</span><span style="color:#7f8989;"># this won't work since 'sky-' is not a single character
</span><span>$ c=</span><span style="color:#d07711;">'sky-rose'
</span><span>$ echo </span><span style="color:#d07711;">"${c^^*-}"
</span><span>sky</span><span style="color:#72ab00;">-</span><span>rose
</span></code></pre>
<p>To convert uppercase to lowercase, you just need to use <code>,</code> instead of <code>^</code>. Sounds simple right? It really is. But, I got stuck while trying to modify the above examples:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ fruit=</span><span style="color:#d07711;">'APPLE'
</span><span>
</span><span style="color:#7f8989;"># worked as expected
</span><span>$ echo </span><span style="color:#d07711;">"${fruit,,}"
</span><span>apple
</span><span>
</span><span style="color:#7f8989;"># expected 'ApplE' but got 'APPLE'
</span><span style="color:#7f8989;"># can you spot the mistake?
</span><span>$ echo </span><span style="color:#d07711;">"${fruit,,[g-z]}"
</span><span style="color:#5597d6;">APPLE
</span></code></pre>
<p>I usually go through documentation and stackexchange sites when I'm stuck. After going through some threads, I came across <a href="https://unix.stackexchange.com/q/500274/109046">this unix.stackexchange</a> example:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ str=</span><span style="color:#d07711;">"HELLO"
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%s</span><span style="color:#d07711;">\n' "${str,,[HEO]}"
</span><span>heLLo
</span></code></pre>
<p>Okay, I thought, this seems similar to what I wanted. Need to check out if this works on my machine. Before I even finished typing the example, my brain's light bulb turned on. I should have used <code>G-Z</code> instead of lowercase range.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ echo </span><span style="color:#d07711;">"${fruit,,[G-Z]}"
</span><span style="color:#5597d6;">ApplE
</span></code></pre>
CLI tip 10: version sort2022-05-13T00:00:00+00:002022-05-13T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-10/<p>You can use <code>sort -V</code> for sorting numerical input that is mixed with other characters. It also helps when you want to treat digits after a decimal point as whole numbers, for example if <code>1.10</code> should be greater than <code>1.2</code>.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'1.5\n1.10\n1.2' </span><span style="color:#72ab00;">|</span><span> sort </span><span style="color:#72ab00;">-</span><span>n
</span><span style="color:#b3933a;">1.10
</span><span style="color:#b3933a;">1.2
</span><span style="color:#b3933a;">1.5
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'1.5\n1.10\n1.2' </span><span style="color:#72ab00;">|</span><span> sort </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">V
</span><span style="color:#b3933a;">1.2
</span><span style="color:#b3933a;">1.5
</span><span style="color:#b3933a;">1.10
</span><span>
</span><span>$ cat versions.txt
</span><span>file2
</span><span>cmd5.</span><span style="color:#b3933a;">2
</span><span>file10
</span><span>cmd1.</span><span style="color:#b3933a;">6
</span><span>file5
</span><span>cmd5.</span><span style="color:#b3933a;">10
</span><span>$ sort </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">V</span><span> versions.txt
</span><span>cmd1.</span><span style="color:#b3933a;">6
</span><span>cmd5.</span><span style="color:#b3933a;">2
</span><span>cmd5.</span><span style="color:#b3933a;">10
</span><span>file2
</span><span>file5
</span><span>file10
</span></code></pre>
<p>Here's an example of dealing with numbers reported by the <code>time</code> command (assuming all the entries have the same format).</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat timings.txt
</span><span>5m35.363s
</span><span>3m20.058s
</span><span>4m11.130s
</span><span>3m42.833s
</span><span>4m3.083s
</span><span>
</span><span>$ sort </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">V</span><span> timings.txt
</span><span>3m20.058s
</span><span>3m42.833s
</span><span>4m3.083s
</span><span>4m11.130s
</span><span>5m35.363s
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://www.gnu.org/software/coreutils/manual/html_node/Version-sort-overview.html">GNU coreutils manual: Version sort ordering</a> for more details. Also, note that the <code>ls</code> command uses lowercase <code>-v</code> for this task.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/QKZbdZ-XmCQ" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://learnbyexample.github.io/cli_text_processing_coreutils/sort.html">sort command</a> chapter from my <a href="https://github.com/learnbyexample/cli_text_processing_coreutils">Command line text processing with GNU Coreutils</a> ebook for more details.</p>
Python tip 10: removeprefix and removesuffix string methods2022-05-11T00:00:00+00:002022-05-11T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-10/<p>Python supports plenty of string methods that reduces the need for regular expressions. The <code>removeprefix()</code> and <code>removesuffix()</code> string methods were added in the Python 3.9 version. See <a href="https://peps.python.org/pep-0616/">PEP 616</a> for more details.</p>
<p>These methods help to delete an exact substring from the start and end of the input string respectively. Here are some examples:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># remove 'sp' if it matches at the start of the input string
</span><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">'spare'</span><span>.</span><span style="color:#5597d6;">removeprefix</span><span>(</span><span style="color:#d07711;">'sp'</span><span>)
</span><span style="color:#d07711;">'are'
</span><span style="color:#7f8989;"># 'par' is present in the input, but not at the start
</span><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">'spare'</span><span>.</span><span style="color:#5597d6;">removeprefix</span><span>(</span><span style="color:#d07711;">'par'</span><span>)
</span><span style="color:#d07711;">'spare'
</span><span>
</span><span style="color:#7f8989;"># remove 'me' if it matches at the end of the input string
</span><span style="color:#7f8989;"># only one occurrence of the match will be removed
</span><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">'this meme'</span><span>.</span><span style="color:#5597d6;">removesuffix</span><span>(</span><span style="color:#d07711;">'me'</span><span>)
</span><span style="color:#d07711;">'this me'
</span><span style="color:#7f8989;"># characters have to be matched exactly in the same order
</span><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">'this meme'</span><span>.</span><span style="color:#5597d6;">removesuffix</span><span>(</span><span style="color:#d07711;">'em'</span><span>)
</span><span style="color:#d07711;">'this meme'
</span></code></pre>
<p>These remove methods will delete the given substring only once from the start or end of the string. On the other hand, the strip methods treat the argument as a set of characters to be matched any number of times in any order until a non-matching character is found. Here are some examples:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">'these memes'</span><span>.</span><span style="color:#5597d6;">removesuffix</span><span>(</span><span style="color:#d07711;">'esm'</span><span>)
</span><span style="color:#d07711;">'these memes'
</span><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">'these memes'</span><span>.</span><span style="color:#5597d6;">rstrip</span><span>(</span><span style="color:#d07711;">'esm'</span><span>)
</span><span style="color:#d07711;">'these '
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">'effective'</span><span>.</span><span style="color:#5597d6;">removeprefix</span><span>(</span><span style="color:#d07711;">'ef'</span><span>)
</span><span style="color:#d07711;">'fective'
</span><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">'effective'</span><span>.</span><span style="color:#5597d6;">lstrip</span><span>(</span><span style="color:#d07711;">'ef'</span><span>)
</span><span style="color:#d07711;">'ctive'
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/b--A6OM3PyI" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Python 3.11: possessive quantifiers and atomic grouping added to re module2022-05-07T00:00:00+00:002023-04-12T00:00:00+00:00https://learnbyexample.github.io/python-regex-possessive-quantifier/<p>Quoting from <a href="https://docs.python.org/3.11/whatsnew/3.11.html">What's New In Python 3.11</a>:</p>
<blockquote>
<p>Atomic grouping (<code>(?>...)</code>) and possessive quantifiers (<code>*+</code>, <code>++</code>, <code>?+</code>, <code>{m,n}+</code>) are now supported in regular expressions. (Contributed by Jeffrey C. Jacobs and Serhiy Storchaka in <a href="https://github.com/python/cpython/issues/34627">bpo-433030</a>.)</p>
</blockquote>
<p align="center"><img src="/images/python_possessive_quantifiers.png" alt="Python possessive quantifiers and atomic grouping" /></p>
<p align="center"><i>Poster created using <a href="https://www.canva.com/">Canva</a></i></p>
<p><img src="/images/info.svg" alt="info" /> If you are not familiar with regular expressions, see my <a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> ebook to get started.</p>
<span id="continue-reading"></span><br>
<h2 id="backtracking">Backtracking<a class="zola-anchor" href="#backtracking" aria-label="Anchor link for: backtracking">🔗</a></h2>
<p>Greedy quantifiers match as much as possible, provided the overall regex is satisfied. For example, <code>:.*</code> will match <code>:</code> followed by rest of the input line. However, if you change the pattern to <code>:.*apple</code>, the <code>.*</code> portion cannot simply consume the rest of the input line. The regex engine will have to find the largest portion such that <code>apple</code> is also part of the match (provided the input has such a string, of course).</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> import </span><span>re
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>ip </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'fig:mango:pineapple:guava:apples:orange'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">:</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*</span><span style="color:#d07711;">'</span><span>, ip)[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#d07711;">':mango:pineapple:guava:apples:orange'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">:</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">apple</span><span style="color:#d07711;">'</span><span>, ip)[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#d07711;">':mango:pineapple:guava:apple'
</span></code></pre>
<p>For the <code>:.*apple</code> case, the Python regular expression engine actually does consume all the characters on seeing <code>.*</code>. Then realizing that the overall match failed, it gives back one character from the end of line and checks again. This process is repeated until a match is found or failure is confirmed. In regular expression parlance, this is called <strong>backtracking</strong>.</p>
<p>This type of exploring matches to satisfy overall regex also applies to non-greedy quantifiers. <code>.*?</code> will start with zero characters followed by one, two, three and so on until a match is found.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>ip </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'fig:mango:pineapple:guava:apples:orange'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">:</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#d07711;">'</span><span>, ip)[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#d07711;">':'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">:</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#7c8f4c;">apple</span><span style="color:#d07711;">'</span><span>, ip)[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#d07711;">':mango:pineapple'
</span></code></pre>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> Note that some regex engines like <a href="https://github.com/google/re2">re2</a> do not use backtracking.</p>
</blockquote>
<br>
<h2 id="possessive-quantifiers">Possessive quantifiers<a class="zola-anchor" href="#possessive-quantifiers" aria-label="Anchor link for: possessive-quantifiers">🔗</a></h2>
<p>Until Python 3.10, you had to use alternatives like the third-party <a href="https://pypi.org/project/regex/">regex module</a> for possessive quantifiers. The <code>re</code> module supports possessive quantifiers from Python 3.11 version.</p>
<p>The difference between greedy and possessive quantifiers is that possessive will not backtrack to find a match. In other words, possessive quantifiers will always consume every character that matches the pattern on which it is applied. Syntax wise, you need to append <code>+</code> to greedy quantifiers to make it possessive, similar to adding <code>?</code> for non-greedy case.</p>
<p>Unlike greedy or non-greedy quantifiers, <code>:.*+apple</code> will never match, because <code>.*+</code> will consume rest of the line, leaving no way to match <code>apple</code>.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span>$ python3</span><span style="color:#b3933a;">.11 </span><span style="color:#72ab00;">-</span><span>q
</span><span style="color:#72ab00;">>>> import </span><span>re
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>ip </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'fig:mango:pineapple:guava:apples:orange'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">:</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*+</span><span style="color:#d07711;">'</span><span>, ip)[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#d07711;">':mango:pineapple:guava:apples:orange'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">bool</span><span>(re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">:</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*+</span><span style="color:#7c8f4c;">apple</span><span style="color:#d07711;">'</span><span>, ip))
</span><span style="color:#b3933a;">False
</span></code></pre>
<p>Here's a more practical example. Suppose you want to match integer numbers greater than or equal to <code>100</code> where these numbers can optionally have leading zeros.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>numbers </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'42 314 001 12 00984'
</span><span>
</span><span style="color:#7f8989;"># this solution fails because 0* and \d{3,} can both match leading zeros
</span><span style="color:#7f8989;"># and greedy quantifiers will give up characters to help overall regex succeed
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">0</span><span style="color:#72ab00;">*</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">{3,}</span><span style="color:#d07711;">'</span><span>, numbers)
</span><span>[</span><span style="color:#d07711;">'314'</span><span>, </span><span style="color:#d07711;">'001'</span><span>, </span><span style="color:#d07711;">'00984'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># here 0*+ will not give back leading zeros after they are consumed
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">0</span><span style="color:#72ab00;">*+</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">{3,}</span><span style="color:#d07711;">'</span><span>, numbers)
</span><span>[</span><span style="color:#d07711;">'314'</span><span>, </span><span style="color:#d07711;">'00984'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># workaround if possessive quantifiers are not supported
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">0</span><span style="color:#72ab00;">*</span><span style="color:#aeb52b;">[</span><span style="color:#b3933a;">1-9</span><span style="color:#aeb52b;">]\d</span><span style="color:#72ab00;">{2,}</span><span style="color:#d07711;">'</span><span>, numbers)
</span><span>[</span><span style="color:#d07711;">'314'</span><span>, </span><span style="color:#d07711;">'00984'</span><span>]
</span></code></pre>
<p>Here's another example. The goal is to match lines whose first non-whitespace character is not a <code>#</code> character. A matching line should have at least one non-<code>#</code> character, so empty lines and those with only whitespace characters should not match.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>lines </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'#comment'</span><span>, </span><span style="color:#d07711;">'c = "#"'</span><span>, </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\t</span><span style="color:#d07711;"> #comment'</span><span>, </span><span style="color:#d07711;">'abc'</span><span>, </span><span style="color:#d07711;">''</span><span>, </span><span style="color:#d07711;">' </span><span style="color:#aeb52b;">\t </span><span style="color:#d07711;">'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># this solution fails because \s* can backtrack
</span><span style="color:#7f8989;"># and [^#] can match a whitespace character as well
</span><span style="color:#72ab00;">>>> </span><span>[e </span><span style="color:#72ab00;">for </span><span>e </span><span style="color:#72ab00;">in </span><span>lines </span><span style="color:#72ab00;">if </span><span>re.</span><span style="color:#5597d6;">match</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\s</span><span style="color:#72ab00;">*</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">#]</span><span style="color:#d07711;">'</span><span>, e)]
</span><span>[</span><span style="color:#d07711;">'c = "#"'</span><span>, </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\t</span><span style="color:#d07711;"> #comment'</span><span>, </span><span style="color:#d07711;">'abc'</span><span>, </span><span style="color:#d07711;">' </span><span style="color:#aeb52b;">\t </span><span style="color:#d07711;">'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># this works because \s*+ will not give back any whitespace characters
</span><span style="color:#72ab00;">>>> </span><span>[e </span><span style="color:#72ab00;">for </span><span>e </span><span style="color:#72ab00;">in </span><span>lines </span><span style="color:#72ab00;">if </span><span>re.</span><span style="color:#5597d6;">match</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\s</span><span style="color:#72ab00;">*+</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">#]</span><span style="color:#d07711;">'</span><span>, e)]
</span><span>[</span><span style="color:#d07711;">'c = "#"'</span><span>, </span><span style="color:#d07711;">'abc'</span><span>]
</span><span>
</span><span style="color:#7f8989;"># workaround if possessive quantifiers are not supported
</span><span style="color:#72ab00;">>>> </span><span>[e </span><span style="color:#72ab00;">for </span><span>e </span><span style="color:#72ab00;">in </span><span>lines </span><span style="color:#72ab00;">if </span><span>re.</span><span style="color:#5597d6;">match</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\s</span><span style="color:#72ab00;">*</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">#</span><span style="color:#b3933a;">\s</span><span style="color:#aeb52b;">]</span><span style="color:#d07711;">'</span><span>, e)]
</span><span>[</span><span style="color:#d07711;">'c = "#"'</span><span>, </span><span style="color:#d07711;">'abc'</span><span>]
</span></code></pre>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/Y7XuZOLdG0o" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<h2 id="atomic-grouping">Atomic grouping<a class="zola-anchor" href="#atomic-grouping" aria-label="Anchor link for: atomic-grouping">🔗</a></h2>
<p><code>(?>pat)</code> is an atomic group, where <code>pat</code> is the pattern you want to safeguard from further backtracking by isolating it from other parts of the regex.</p>
<p>Here's an example with greedy quantifier:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>numbers </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'42 314 001 12 00984'
</span><span>
</span><span style="color:#7f8989;"># 0* is greedy and the (?>) grouping prevents backtracking
</span><span style="color:#7f8989;"># same as: re.findall(r'0*+\d{3,}', numbers)
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">findall</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#72ab00;">?</span><span style="color:#7c8f4c;">>0</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">)</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">{3,}</span><span style="color:#d07711;">'</span><span>, numbers)
</span><span>[</span><span style="color:#d07711;">'314'</span><span>, </span><span style="color:#d07711;">'00984'</span><span>]
</span></code></pre>
<p>Here's an example with non-greedy quantifier:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>ip </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'fig::mango::pineapple::guava::apples::orange'
</span><span>
</span><span style="color:#7f8989;"># this matches from the first '::' to the first occurrence of '::apple'
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">::</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#7c8f4c;">::apple</span><span style="color:#d07711;">'</span><span>, ip)[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#d07711;">'::mango::pineapple::guava::apple'
</span><span>
</span><span style="color:#7f8989;"># '(?>::.*?::)' will match only from '::' to the very next '::'
</span><span style="color:#7f8989;"># '::mango::' fails because 'apple' isn't found afterwards
</span><span style="color:#7f8989;"># similarly '::pineapple::' fails
</span><span style="color:#7f8989;"># '::guava::' succeeds because it is followed by 'apple'
</span><span style="color:#72ab00;">>>> </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#72ab00;">?</span><span style="color:#7c8f4c;">>::</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#7c8f4c;">::)apple</span><span style="color:#d07711;">'</span><span>, ip)[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#d07711;">'::guava::apple'
</span></code></pre>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/DQfcST_XN_E" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> The <a href="https://pypi.org/project/regex/">regex module</a> has a <code>regex.REVERSE</code> flag to match from right-to-left making it better suited than atomic grouping for certain cases.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> import </span><span>regex
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>ip </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'fig::mango::pineapple::guava::apples::orange'
</span><span style="color:#72ab00;">>>> </span><span>regex.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#72ab00;">?</span><span style="color:#7c8f4c;">r)::</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#7c8f4c;">::apple</span><span style="color:#d07711;">'</span><span>, ip)[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#d07711;">'::guava::apple'
</span><span>
</span><span style="color:#7f8989;"># this won't be possible with just atomic grouping
</span><span style="color:#72ab00;">>>> </span><span>ip </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'and this book is good and those are okay and that movie is bad'
</span><span style="color:#72ab00;">>>> </span><span>regex.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(</span><span style="color:#72ab00;">?</span><span style="color:#7c8f4c;">r)th</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?\b</span><span style="color:#7c8f4c;">is bad</span><span style="color:#d07711;">'</span><span>, ip)[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#d07711;">'that movie is bad'
</span></code></pre>
</blockquote>
<br>
<h2 id="catastrophic-backtracking">Catastrophic Backtracking<a class="zola-anchor" href="#catastrophic-backtracking" aria-label="Anchor link for: catastrophic-backtracking">🔗</a></h2>
<p>Backtracking can become significantly time consuming for certain corner cases. Which is why some regex engines do not use them, at the cost of not supporting some features like lookarounds. If your application accepts user defined regex, you might need to protect against such catastrophic patterns. From <a href="https://en.wikipedia.org/wiki/Redos">wikipedia: ReDoS</a>:</p>
<blockquote>
<p>A regular expression denial of service (ReDoS) is an algorithmic complexity attack that produces a denial-of-service by providing a regular expression and/or an input that takes a long time to evaluate. The attack exploits the fact that many regular expression implementations have super-linear worst-case complexity; on certain regex-input pairs, the time taken can grow polynomially or exponentially in relation to the input size. An attacker can thus cause a program to spend substantial time by providing a specially crafted regular expression and/or input. The program will then slow down or becoming unresponsive.</p>
</blockquote>
<p>Here's an example:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> from </span><span>timeit </span><span style="color:#72ab00;">import </span><span>timeit
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>greedy </span><span style="color:#72ab00;">= </span><span>re.</span><span style="color:#5597d6;">compile</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(a</span><span style="color:#72ab00;">+|</span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">:</span><span style="color:#d07711;">'</span><span>)
</span><span style="color:#72ab00;">>>> </span><span>possessive </span><span style="color:#72ab00;">= </span><span>re.</span><span style="color:#5597d6;">compile</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(a</span><span style="color:#72ab00;">+|</span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">+</span><span style="color:#7c8f4c;">)</span><span style="color:#72ab00;">*+</span><span style="color:#7c8f4c;">:</span><span style="color:#d07711;">'</span><span>)
</span><span>
</span><span style="color:#7f8989;"># string that'll match the above patterns
</span><span style="color:#72ab00;">>>> </span><span>s1 </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'aaaaaaaaaaaaaaaa:123'
</span><span style="color:#7f8989;"># string that does NOT match the above patterns
</span><span style="color:#72ab00;">>>> </span><span>s2 </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'aaaaaaaaaaaaaaaa-123'
</span><span>
</span><span style="color:#7f8989;"># no issues when input string has a match
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">timeit</span><span>(</span><span style="color:#d07711;">'greedy.search(s1)'</span><span>, </span><span style="color:#5597d6;">number</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">10000</span><span>, </span><span style="color:#5597d6;">globals</span><span style="color:#72ab00;">=</span><span style="color:#b39f04;">globals</span><span>())
</span><span style="color:#b3933a;">0.016464739997900324
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">timeit</span><span>(</span><span style="color:#d07711;">'possessive.search(s1)'</span><span>, </span><span style="color:#5597d6;">number</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">10000</span><span>, </span><span style="color:#5597d6;">globals</span><span style="color:#72ab00;">=</span><span style="color:#b39f04;">globals</span><span>())
</span><span style="color:#b3933a;">0.016358205997676123
</span><span>
</span><span style="color:#7f8989;"># if input doesn't match, greedy version suffers from catastrophic backtracking
</span><span style="color:#7f8989;"># note that 'number' parameter is reduced to 10 since it takes a long time
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">timeit</span><span>(</span><span style="color:#d07711;">'greedy.search(s2)'</span><span>, </span><span style="color:#5597d6;">number</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">10</span><span>, </span><span style="color:#5597d6;">globals</span><span style="color:#72ab00;">=</span><span style="color:#b39f04;">globals</span><span>())
</span><span style="color:#b3933a;">53.71723825200024
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">timeit</span><span>(</span><span style="color:#d07711;">'possessive.search(s2)'</span><span>, </span><span style="color:#5597d6;">number</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">10</span><span>, </span><span style="color:#5597d6;">globals</span><span style="color:#72ab00;">=</span><span style="color:#b39f04;">globals</span><span>())
</span><span style="color:#b3933a;">0.00019008600065717474
</span></code></pre>
<p><code>(a+|\w+)*:</code> is a silly regex pattern, since it can be rewritten as <code>\w*:</code> which will not suffer from catastrophic backtracking. But this example shows how quantifiers applied to a group with multiple alternatives using quantifiers can lead to explosive results. More such patterns and mitigation strategies can be found in the following links:</p>
<ul>
<li><a href="https://www.rexegg.com/regex-explosive-quantifiers.html">The Explosive Quantifier Trap</a></li>
<li><a href="https://www.regular-expressions.info/catastrophic.html">Runaway Regular Expressions: Catastrophic Backtracking</a></li>
<li><a href="https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019/">Details of the Cloudflare outage on July 2, 2019</a></li>
</ul>
Vim tip 8: join lines2022-05-04T00:00:00+00:002022-05-04T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-8/<p>In Normal mode, you can join lines using <code>J</code> and <code>gJ</code> commands. These differ in how the end-of-line character and indentation at the start of lines being joined are handled.</p>
<ul>
<li><kbd>J</kbd> joins the current line and the next line
<ul>
<li>the deleted <code><EOL></code> character is replaced with a space (unless there are trailing spaces or the next line starts with a <code>)</code> character)</li>
<li>indentation from the lines being joined are removed, <em>except the current line</em></li>
</ul>
</li>
<li><kbd>3J</kbd> joins the current line and next two lines with one space in between the lines</li>
<li><kbd>gJ</kbd> joins the current line and the next line
<ul>
<li><code><EOL></code> character is deleted (space character won't be added)</li>
<li>indentation won't be removed</li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> <code>joinspaces</code>, <code>cpoptions</code> and <code>formatoptions</code> settings will affect the behavior of these commands. See <a href="https://vimhelp.org/change.txt.html#J">:h J</a> and scroll down for more details.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/rubhH6v4lN0" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 9: awk paragraph mode2022-04-27T00:00:00+00:002022-04-27T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-9/<p><code>awk</code> provides a handy shortcut to process input content paragraph wise. When <code>RS</code> is set to empty string, one or more consecutive empty lines is used as the input record separator. Consider the below sample file:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat para.txt
</span><span>hi there
</span><span>how are you
</span><span>
</span><span style="color:#b3933a;">2</span><span> apples
</span><span style="color:#b3933a;">12</span><span> bananas
</span><span>
</span><span>
</span><span>blue sky
</span><span>yellow sun
</span><span>brown earth
</span></code></pre>
<p>Here are some simple examples to filter paragraphs based on some criteria:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># paragraphs containing 'sun'
</span><span>$ awk </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">RS</span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'/sun/'</span><span> para.txt
</span><span>blue sky
</span><span>yellow sun
</span><span>brown earth
</span><span>
</span><span style="color:#7f8989;"># paragraphs containing any digit character
</span><span>$ awk </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">RS</span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'/[0-9]/'</span><span> para.txt
</span><span style="color:#b3933a;">2</span><span> apples
</span><span style="color:#b3933a;">12</span><span> bananas
</span><span>
</span><span style="color:#7f8989;"># print the first paragraph
</span><span>$ awk </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">RS</span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'NR==1'</span><span> para.txt
</span><span>hi there
</span><span>how are you
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://learnbyexample.github.io/learn_gnuawk/record-separators.html#paragraph-mode">Paragraph mode section from my GNU awk ebook</a> for more examples and corner cases.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/OmljHVH-SCw" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/learn_gnuawk">CLI text processing with GNU awk</a> ebook if you are interested in learning about the <code>GNU awk</code> command in more detail.</p>
Python tip 9: applying set-like operations for dictionaries2022-04-16T00:00:00+00:002022-04-16T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-9/<p>You can merge two dictionaries using the <code>|</code> operator (similar to union of sets). If a key is found in both the dictionaries, the insertion order of the first dictionary will be maintained, but the value of the second dictionary will be used. In other words, keys are updated to the new value during the merge.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>marks_1 </span><span style="color:#72ab00;">= </span><span>{</span><span style="color:#d07711;">'Rahul'</span><span>: </span><span style="color:#b3933a;">86</span><span>, </span><span style="color:#d07711;">'Ravi'</span><span>: </span><span style="color:#b3933a;">92</span><span>, </span><span style="color:#d07711;">'Rohit'</span><span>: </span><span style="color:#b3933a;">75</span><span>}
</span><span style="color:#72ab00;">>>> </span><span>marks_2 </span><span style="color:#72ab00;">= </span><span>{</span><span style="color:#d07711;">'Jo'</span><span>: </span><span style="color:#b3933a;">89</span><span>, </span><span style="color:#d07711;">'Rohit'</span><span>: </span><span style="color:#b3933a;">78</span><span>, </span><span style="color:#d07711;">'Joe'</span><span>: </span><span style="color:#b3933a;">75</span><span>, </span><span style="color:#d07711;">'Ravi'</span><span>: </span><span style="color:#b3933a;">100</span><span>}
</span><span>
</span><span style="color:#7f8989;"># use unpacking, i.e. {**d1, **d2} for Python 3.8 and below versions
</span><span style="color:#72ab00;">>>> </span><span>marks_1 </span><span style="color:#72ab00;">| </span><span>marks_2
</span><span>{</span><span style="color:#d07711;">'Rahul'</span><span>: </span><span style="color:#b3933a;">86</span><span>, </span><span style="color:#d07711;">'Ravi'</span><span>: </span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#d07711;">'Rohit'</span><span>: </span><span style="color:#b3933a;">78</span><span>, </span><span style="color:#d07711;">'Jo'</span><span>: </span><span style="color:#b3933a;">89</span><span>, </span><span style="color:#d07711;">'Joe'</span><span>: </span><span style="color:#b3933a;">75</span><span>}
</span></code></pre>
<p>Use <code>update()</code> method if you want to modify instead of getting a new dictionary.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>marks_1.</span><span style="color:#5597d6;">update</span><span>(marks_2)
</span><span style="color:#72ab00;">>>> </span><span>marks_1
</span><span>{</span><span style="color:#d07711;">'Rahul'</span><span>: </span><span style="color:#b3933a;">86</span><span>, </span><span style="color:#d07711;">'Ravi'</span><span>: </span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#d07711;">'Rohit'</span><span>: </span><span style="color:#b3933a;">78</span><span>, </span><span style="color:#d07711;">'Jo'</span><span>: </span><span style="color:#b3933a;">89</span><span>, </span><span style="color:#d07711;">'Joe'</span><span>: </span><span style="color:#b3933a;">75</span><span>}
</span></code></pre>
<p>The <code>keys()</code> and <code>values()</code> dictionary methods return set-like objects, but with insertion order maintained. You get a set object as output when you apply set operators on these objects.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>marks_1 </span><span style="color:#72ab00;">= </span><span>{</span><span style="color:#d07711;">'Rahul'</span><span>: </span><span style="color:#b3933a;">86</span><span>, </span><span style="color:#d07711;">'Ravi'</span><span>: </span><span style="color:#b3933a;">92</span><span>, </span><span style="color:#d07711;">'Rohit'</span><span>: </span><span style="color:#b3933a;">75</span><span>}
</span><span style="color:#72ab00;">>>> </span><span>marks_2 </span><span style="color:#72ab00;">= </span><span>{</span><span style="color:#d07711;">'Jo'</span><span>: </span><span style="color:#b3933a;">89</span><span>, </span><span style="color:#d07711;">'Rohit'</span><span>: </span><span style="color:#b3933a;">78</span><span>, </span><span style="color:#d07711;">'Joe'</span><span>: </span><span style="color:#b3933a;">75</span><span>, </span><span style="color:#d07711;">'Ravi'</span><span>: </span><span style="color:#b3933a;">100</span><span>}
</span><span>
</span><span style="color:#7f8989;"># union of keys
</span><span style="color:#72ab00;">>>> </span><span>marks_1.</span><span style="color:#5597d6;">keys</span><span>() </span><span style="color:#72ab00;">| </span><span>marks_2.</span><span style="color:#5597d6;">keys</span><span>()
</span><span>{</span><span style="color:#d07711;">'Rohit'</span><span>, </span><span style="color:#d07711;">'Rahul'</span><span>, </span><span style="color:#d07711;">'Ravi'</span><span>, </span><span style="color:#d07711;">'Jo'</span><span>, </span><span style="color:#d07711;">'Joe'</span><span>}
</span><span>
</span><span style="color:#7f8989;"># common keys
</span><span style="color:#72ab00;">>>> </span><span>marks_1.</span><span style="color:#5597d6;">keys</span><span>() </span><span style="color:#72ab00;">& </span><span>marks_2.</span><span style="color:#5597d6;">keys</span><span>()
</span><span>{</span><span style="color:#d07711;">'Ravi'</span><span>, </span><span style="color:#d07711;">'Rohit'</span><span>}
</span><span>
</span><span style="color:#7f8989;"># difference: keys not present in the other dict
</span><span style="color:#72ab00;">>>> </span><span>marks_1.</span><span style="color:#5597d6;">keys</span><span>() </span><span style="color:#72ab00;">- </span><span>marks_2.</span><span style="color:#5597d6;">keys</span><span>()
</span><span>{</span><span style="color:#d07711;">'Rahul'</span><span>}
</span><span style="color:#72ab00;">>>> </span><span>marks_2.</span><span style="color:#5597d6;">keys</span><span>() </span><span style="color:#72ab00;">- </span><span>marks_1.</span><span style="color:#5597d6;">keys</span><span>()
</span><span>{</span><span style="color:#d07711;">'Jo'</span><span>, </span><span style="color:#d07711;">'Joe'</span><span>}
</span><span>
</span><span style="color:#7f8989;"># symmetric difference: union of above two differences
</span><span style="color:#72ab00;">>>> </span><span>marks_1.</span><span style="color:#5597d6;">keys</span><span>() </span><span style="color:#72ab00;">^ </span><span>marks_2.</span><span style="color:#5597d6;">keys</span><span>()
</span><span>{</span><span style="color:#d07711;">'Jo'</span><span>, </span><span style="color:#d07711;">'Joe'</span><span>, </span><span style="color:#d07711;">'Rahul'</span><span>}
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/ILI7RQnKaZE" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 7: changing case in Normal mode2022-04-12T00:00:00+00:002022-04-12T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-7/<p>You can use the following commands to change the case of characters:</p>
<ul>
<li><kbd>~</kbd> invert the case of the character under the cursor (i.e. lowercase becomes UPPERCASE and vice versa)</li>
<li><kbd>g~</kbd> followed by motion inverts the case of those characters
<ul>
<li>for example: <kbd>g~e</kbd>, <kbd>g~$</kbd>, <kbd>g~iw</kbd>, etc </li>
</ul>
</li>
<li><kbd>gu</kbd> followed by motion changes those characters to lowercase
<ul>
<li>for example: <kbd>gue</kbd>, <kbd>gu$</kbd>, <kbd>guiw</kbd>, etc </li>
</ul>
</li>
<li><kbd>gU</kbd> followed by motion changes those characters to UPPERCASE
<ul>
<li>for example: <kbd>gUe</kbd>, <kbd>gU$</kbd>, <kbd>gUiw</kbd>, etc </li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> You can also provide a count prefix to these commands. For example, <kbd>3~</kbd> will invert the case of the current character and two characters to the right.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/TLhEznBrEG8" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 8: extract from start of file until matching line2022-04-06T00:00:00+00:002022-04-06T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-8/<p>The <code>GNU sed</code> command has a couple of handy commands to extract text from the start of input until a matching line is found. The <code>q</code> and <code>Q</code> commands are similar, except how they process the matching line.</p>
<p>The <code>q</code> command will exit <code>sed</code> immediately, after printing the current pattern space if applicable.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># quit after a line containing 'st' is found
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'apple\nsea\neast\ndust' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'/st/q'
</span><span>apple
</span><span>sea
</span><span>east
</span></code></pre>
<p>The <code>Q</code> command is similar to <code>q</code> but won't print the matching line.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># matching line won't be printed in this case
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'apple\nsea\neast\ndust' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'/st/Q'
</span><span>apple
</span><span>sea
</span></code></pre>
<p><code>tac+sed+tac</code> will help you get lines starting from the last occurrence of the search string till the end of the input.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'apple\nsea\neast\ndust\n' </span><span style="color:#72ab00;">|</span><span> tac </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'/ea/q' </span><span style="color:#72ab00;">|</span><span> tac
</span><span>east
</span><span>dust
</span></code></pre>
<blockquote>
<p><img src="/images/warning.svg" alt="warning" /> Be careful if you want to use <code>q</code> or <code>Q</code> commands with multiple files, as <code>sed</code> will stop even if there are other files left to be processed. You can use <a href="https://learnbyexample.github.io/learn_gnused/selective-editing.html#address-range">mixed address ranges</a> as a workaround. See also <a href="https://unix.stackexchange.com/q/309514/109046">unix.stackexchange: applying q to multiple files</a>.</p>
</blockquote>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/OA6Onqw2bfA" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/learn_gnused">CLI text processing with GNU sed</a> ebook if you are interested in learning about the <code>GNU sed</code> command in more detail.</p>
Vim Reference Guide: two week sales report2022-03-31T00:00:00+00:002022-03-31T00:00:00+00:00https://learnbyexample.github.io/mini/vim-reference-guide-sales/<p>I've previously written about events and strategies that led to <a href="https://learnbyexample.github.io/wild-ride-2021/#book-sales">increased ebook sales during the last quarter of 2021</a>.</p>
<p>Very pleased to inform that I continue to see more than expected sales. I had released my 12th ebook <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> on March 15th. Here's how the sales looked on Gumroad during the first two weeks:</p>
<p align="center"><img src="/images/vim-release-gumroad-sales.png" alt="Two week Gumroad sales chart" /></p>
<p>I used to offer my ebooks for free on release. For the past couple of releases, I have also added heavily discounted ebook bundles which seems to be the major factor in increased paid sales I'm seeing. Luck certainly plays a role too, reaching front page of Hacker News and top of subreddits cannot be always counted upon. Here are some of the ways I promoted my latest ebook:</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/p/announcing-vim-reference-guide-free-discount-offers-and-more">Announcement post on Gumroad</a> and sending an email to existing readers</li>
<li><a href="https://news.ycombinator.com/item?id=30684232">Show HN post on Hacker News</a>, got lucky to be placed in top 10</li>
<li><a href="https://twitter.com/learn_byexample/status/1503752985708736512">Pinned tweet</a></li>
<li>Posting on <a href="https://www.reddit.com/r/commandline/comments/tjzxn9/i_wrote_a_vim_reference_guide/">/r/commandline/</a> and <a href="https://www.reddit.com/r/linux/comments/toll6i/i_wrote_a_vim_reference_guide/">/r/linux/</a>
<ul>
<li>You might wonder why not /r/vim? Somebody else posted before I could, and unfortunately it got downvoted. I'll probably make my own post after I release the next version</li>
</ul>
</li>
<li><a href="https://youtu.be/SyQe6zzOGZ0">Promo video on youtube</a></li>
<li>Mentioned in two of my <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> newsletter issues</li>
<li>And of course, I wrote a release post <a href="https://learnbyexample.github.io/vim-reference-guide-announcement/">on this blog</a> and also mentioned it on my <a href="https://github.com/learnbyexample">GitHub Readme</a></li>
</ul>
<p>Apart from Gumroad, 500+ readers downloaded the guide from <a href="https://leanpub.com/vim_reference_guide">Leanpub</a> and I got a few paid sales as well. I wrote about <a href="https://learnbyexample.github.io/my-book-writing-experience/#leanpub-vs-gumroad">pros and cons of Gumroad/Leanpub here</a>.</p>
<hr />
<p><img src="/images/info.svg" alt="info" /> PS: Make sure to read the rules and be a regular user before self-promoting your content on the social media platforms mentioned above.</p>
Python tip 8: dict.fromkeys() method2022-03-30T00:00:00+00:002022-03-30T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-8/<p>A lesser known way to create a dictionary is to use the <code>fromkeys()</code> method that accepts an iterable and an optional value (default is <code>None</code>). The same value will be assigned to all the keys, so be careful if you want to use a mutable object.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>colors </span><span style="color:#72ab00;">= </span><span>(</span><span style="color:#d07711;">'red'</span><span>, </span><span style="color:#d07711;">'blue'</span><span>, </span><span style="color:#d07711;">'green'</span><span>)
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">dict</span><span>.</span><span style="color:#5597d6;">fromkeys</span><span>(colors)
</span><span>{</span><span style="color:#d07711;">'red'</span><span>: </span><span style="color:#b3933a;">None</span><span>, </span><span style="color:#d07711;">'blue'</span><span>: </span><span style="color:#b3933a;">None</span><span>, </span><span style="color:#d07711;">'green'</span><span>: </span><span style="color:#b3933a;">None</span><span>}
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">dict</span><span>.</span><span style="color:#5597d6;">fromkeys</span><span>(colors, </span><span style="color:#b3933a;">255</span><span>)
</span><span>{</span><span style="color:#d07711;">'red'</span><span>: </span><span style="color:#b3933a;">255</span><span>, </span><span style="color:#d07711;">'blue'</span><span>: </span><span style="color:#b3933a;">255</span><span>, </span><span style="color:#d07711;">'green'</span><span>: </span><span style="color:#b3933a;">255</span><span>}
</span></code></pre>
<p>When you iterate over a dictionary object, you'll get only the keys. For example:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>fruits </span><span style="color:#72ab00;">= </span><span>{</span><span style="color:#d07711;">'banana'</span><span>: </span><span style="color:#b3933a;">12</span><span>, </span><span style="color:#d07711;">'papaya'</span><span>: </span><span style="color:#b3933a;">5</span><span>, </span><span style="color:#d07711;">'mango'</span><span>: </span><span style="color:#b3933a;">10</span><span>}
</span><span>
</span><span style="color:#72ab00;">>>> for </span><span>fruit </span><span style="color:#72ab00;">in </span><span>fruits:
</span><span style="color:#b3933a;">... </span><span style="color:#b39f04;">print</span><span>(fruit)
</span><span style="color:#b3933a;">...
</span><span>banana
</span><span>papaya
</span><span>mango
</span></code></pre>
<p>Recent Python versions ensure that the insertion order is maintained for a dictionary. So, you can remove duplicate items from a list while maintaining the order by building a dictionary using the <code>fromkeys()</code> method and converting it back to a list.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>nums </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">4</span><span>, </span><span style="color:#b3933a;">6</span><span>, </span><span style="color:#b3933a;">22</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">5</span><span>, </span><span style="color:#b3933a;">4</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">6</span><span>, </span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">51</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">1</span><span>]
</span><span>
</span><span style="color:#7f8989;"># remove duplicates, if you don't care about the element order
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">list</span><span>(</span><span style="color:#a2a001;">set</span><span>(nums))
</span><span>[</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">4</span><span>, </span><span style="color:#b3933a;">5</span><span>, </span><span style="color:#b3933a;">6</span><span>, </span><span style="color:#b3933a;">51</span><span>, </span><span style="color:#b3933a;">22</span><span>]
</span><span>
</span><span style="color:#7f8989;"># remove duplicates, if you want to maintain the element order
</span><span style="color:#72ab00;">>>> </span><span style="color:#a2a001;">list</span><span>(</span><span style="color:#a2a001;">dict</span><span>.</span><span style="color:#5597d6;">fromkeys</span><span>(nums))
</span><span>[</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">4</span><span>, </span><span style="color:#b3933a;">6</span><span>, </span><span style="color:#b3933a;">22</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">5</span><span>, </span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#b3933a;">51</span><span>]
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/ChL65D87uJg" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Vim tip 6: search word nearest to the cursor2022-03-24T00:00:00+00:002022-03-24T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-6/<p>Vim provides handy commands to match words under (or near to) the cursor. You can choose to match whole or part of a longer word. If a match is found, the cursor will move to the next match in the chosen direction.</p>
<ul>
<li><kbd>*</kbd> searches the word nearest to the cursor in the forward direction (matches only the whole word)
<ul>
<li><kbd>Shift</kbd> followed by <strong>left mouse click</strong> can also be used if mouse is enabled</li>
</ul>
</li>
<li><kbd>g*</kbd> searches the word nearest to the cursor in the forward direction (matches as part of another word as well)
<ul>
<li>for example, if you apply this command on the word <code>the</code>, you'll also get matches for <code>them</code>, <code>lather</code>, etc</li>
</ul>
</li>
<li><kbd>#</kbd> searches the word nearest to the cursor in the backward direction (matches only the whole word)</li>
<li><kbd>g#</kbd> searches the word nearest to the cursor in the backward direction (matches as part of another word as well)</li>
</ul>
<p>From <a href="https://vimhelp.org/motion.txt.html#word">:h word</a>:</p>
<blockquote>
<p>A word consists of a sequence of letters, digits and underscores, or a sequence of other non-blank characters, separated with white space (spaces, tabs, <code><EOL></code>). This can be changed with the <code>iskeyword</code> option.</p>
</blockquote>
<p>Sequence of non-blank characters will be used only at the end of the line. Otherwise, the above commands will use the next word found on that line which is made up of letters, digits and underscores.</p>
<p><img src="/images/info.svg" alt="info" /> You can also provide a count prefix to these commands. For example, <kbd>2*</kbd> will take you to the second match in the forward direction.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/abDN559M5yQ" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 7: limiting number of filtered lines2022-03-16T00:00:00+00:002022-06-20T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-7/<p><code>grep</code> supports <code>-m</code> option to specify the maximum number of matching lines in the output.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># all input lines containing 'a'
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'goal\nrate\neat\npit\n' </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#d07711;">'a'
</span><span>goal
</span><span>rate
</span><span>eat
</span><span>
</span><span style="color:#7f8989;"># maximum of 2 matching lines
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'goal\nrate\neat\npit\n' </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>m2 </span><span style="color:#d07711;">'a'
</span><span>goal
</span><span>rate
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'goal\nrate\neat\npit\n' </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>m2 </span><span style="color:#d07711;">'pi'
</span><span>pit
</span><span>
</span><span style="color:#7f8989;"># example with -v option
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'goal\nrate\neat\npit\n' </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#d07711;">'e'
</span><span>goal
</span><span>pit
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'goal\nrate\neat\npit\n' </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#72ab00;">-</span><span>m1 </span><span style="color:#d07711;">'e'
</span><span>goal
</span></code></pre>
<p>With multiple file input, the restriction is applied for each file <em>separately</em>.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat table.txt
</span><span>brown bread mat cake </span><span style="color:#b3933a;">42
</span><span>blue cake mug shirt </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">7
</span><span>yellow banana window shoes </span><span style="color:#b3933a;">3.14
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'goal\nrate\neat\npit\n' </span><span style="color:#72ab00;">></span><span> ip.txt
</span><span>
</span><span>$ grep </span><span style="color:#72ab00;">-</span><span>m1 </span><span style="color:#d07711;">'i'</span><span> table.txt ip.txt
</span><span>table.txt</span><span style="color:#72ab00;">:</span><span>blue cake mug shirt </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">7
</span><span>ip.txt</span><span style="color:#72ab00;">:</span><span>pit
</span><span>
</span><span style="color:#7f8989;"># use 'cat' if you want to operate on combined input
</span><span>$ cat table.txt ip.txt </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>m1 </span><span style="color:#d07711;">'i'
</span><span>blue cake mug shirt </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">7
</span><span>$ cat table.txt ip.txt </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>m1 </span><span style="color:#d07711;">'go'
</span><span>goal
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/9W5jNjmE_04" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep">CLI text processing with GNU grep and ripgrep</a> ebook if you are interested in learning about <code>GNU grep</code> and <code>ripgrep</code> commands in more detail.</p>
Python tip 7: creating a deepcopy of collections2022-03-09T00:00:00+00:002022-06-20T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-7/<p>From <a href="https://docs.python.org/3/library/copy.html#module-copy">copy</a> built-in module:</p>
<blockquote>
<p>Assignment statements in Python do not copy objects, they create bindings between a target and an object. For collections that are mutable or contain mutable items, a copy is sometimes needed so one can change one copy without changing the other.</p>
</blockquote>
<p>The shared binding is helpful for cases like in-place modification of lists within a user defined function:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span style="background-color:#562d56bf;color:#f8f8f8;">def</span><span> </span><span style="color:#5597d6;">rotate</span><span>(ip):
</span><span style="color:#b3933a;">... </span><span>ip.</span><span style="color:#5597d6;">insert</span><span>(</span><span style="color:#b3933a;">0</span><span>, ip.</span><span style="color:#5597d6;">pop</span><span>())
</span><span style="color:#b3933a;">...
</span><span style="color:#72ab00;">>>> </span><span>nums </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#b3933a;">321</span><span>, </span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#b3933a;">5.3</span><span>, </span><span style="color:#b3933a;">2</span><span>]
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">rotate</span><span>(nums)
</span><span style="color:#72ab00;">>>> </span><span>nums
</span><span>[</span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#b3933a;">321</span><span>, </span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#b3933a;">5.3</span><span>]
</span></code></pre>
<p>You can use the <code>copy.deepcopy()</code> method if you wish to recursively create new copies of all the elements of a mutable object:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> import </span><span>copy
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>nums_2d </span><span style="color:#72ab00;">= </span><span>[[</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#b3933a;">10</span><span>], [</span><span style="color:#b3933a;">1.2</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">0.2</span><span>, </span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#b3933a;">2</span><span>], [</span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#b3933a;">200</span><span>]]
</span><span style="color:#72ab00;">>>> </span><span>nums_2d_deepcopy </span><span style="color:#72ab00;">= </span><span>copy.</span><span style="color:#5597d6;">deepcopy</span><span>(nums_2d)
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>nums_2d_deepcopy[</span><span style="color:#b3933a;">0</span><span>][</span><span style="color:#b3933a;">0</span><span>] </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'yay'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>nums_2d_deepcopy
</span><span>[[</span><span style="color:#d07711;">'yay'</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#b3933a;">10</span><span>], [</span><span style="color:#b3933a;">1.2</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">0.2</span><span>, </span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#b3933a;">2</span><span>], [</span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#b3933a;">200</span><span>]]
</span><span style="color:#72ab00;">>>> </span><span>nums_2d
</span><span>[[</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#b3933a;">10</span><span>], [</span><span style="color:#b3933a;">1.2</span><span>, </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">0.2</span><span>, </span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#b3933a;">2</span><span>], [</span><span style="color:#b3933a;">100</span><span>, </span><span style="color:#b3933a;">200</span><span>]]
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/nbBYeNPQ-no" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://learnbyexample.github.io/100_page_python_intro/mutability.html">Mutability</a> chapter from my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook for more details on this topic.</p>
Vim tip 5: jumping back and forth in Normal mode2022-03-02T00:00:00+00:002022-03-02T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-5/<p>Find yourself working on a large file? Or perhaps handling multiple buffers? Vim makes it easy to navigate previous locations:</p>
<ul>
<li><kbd>Ctrl</kbd>+<kbd>o</kbd> navigate to the previous location in the jump list (think <code>o</code> as old)</li>
<li><kbd>Ctrl</kbd>+<kbd>i</kbd> navigate to the next location in the jump list (<code>i</code> and <code>o</code> are usually next to each other)</li>
<li><kbd>g;</kbd> go to the previous change location</li>
<li><kbd>g,</kbd> go to the newer change location</li>
<li><kbd>gi</kbd> place the cursor at the same position where it was left last time in the Insert mode</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> Use <kbd>:jumps</kbd> to view the jump list. See <a href="https://vimhelp.org/motion.txt.html#jump-motions">:h jump-motions</a> for more details.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/eId_8aCcmQE" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 6: filtering lines based on multiple conditions2022-02-23T00:00:00+00:002022-06-17T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-6/<p>Usually, <code>grep</code> is used for filtering lines based on a regexp pattern. Matching multiple patterns as a conditional OR is easy to apply using alternation. For example, <code>grep -E 'bread|banana'</code> matches lines containing either <code>bread</code> or <code>banana</code>.</p>
<p>However, conditional AND requires multiple <code>grep</code> commands or the use of lookarounds if PCRE feature is available. A simpler alternative is to use <code>awk</code>.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat table.txt
</span><span>brown bread mat cake </span><span style="color:#b3933a;">42
</span><span>blue cake mug shirt </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">7
</span><span>yellow banana window shoes </span><span style="color:#b3933a;">3.14
</span><span>
</span><span style="color:#7f8989;"># line containing 'cake' but not 'at'
</span><span style="color:#7f8989;"># same as: grep 'cake' table.txt | grep -v 'at'
</span><span style="color:#7f8989;"># with PCRE: grep -P '^(?!.*at).*cake' table.txt
</span><span>$ awk </span><span style="color:#d07711;">'/cake/ && !/at/'</span><span> table.txt
</span><span>blue cake mug shirt </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">7
</span><span>
</span><span style="color:#7f8989;"># first field containing 'low' or the last field is less than 0
</span><span style="color:#7f8989;"># not easy to construct a grep solution here due to field/numeric comparison
</span><span>$ awk </span><span style="color:#d07711;">'$1 ~ /low/ || $NF<0'</span><span> table.txt
</span><span>blue cake mug shirt </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">7
</span><span>yellow banana window shoes </span><span style="color:#b3933a;">3.14
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/Gasxb9wiEKk" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/learn_gnuawk">CLI text processing with GNU awk</a> ebook if you are interested in learning about the <code>awk</code> command in more detail.</p>
Python tip 6: inplace file editing2022-02-15T00:00:00+00:002022-06-17T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-6/<p>The built-in <a href="https://docs.python.org/3/library/fileinput.html">fileinput module</a> has nice features for file processing, especially handy for multiple files and inplace editing. Here's an example:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#7f8989;"># inplace.py
</span><span style="color:#72ab00;">import </span><span>fileinput
</span><span>
</span><span style="color:#72ab00;">with </span><span>fileinput.</span><span style="color:#5597d6;">input</span><span>(</span><span style="color:#5597d6;">inplace</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">True</span><span>) </span><span style="color:#72ab00;">as </span><span>f:
</span><span> </span><span style="color:#72ab00;">for </span><span>line </span><span style="color:#72ab00;">in </span><span>f:
</span><span> </span><span style="color:#7f8989;"># do some processing
</span><span> op </span><span style="color:#72ab00;">= </span><span>line.</span><span style="color:#5597d6;">replace</span><span>(</span><span style="color:#d07711;">'search'</span><span>, </span><span style="color:#d07711;">'replace'</span><span>)
</span><span> </span><span style="color:#7f8989;"># print() the text you want to write back to the input files
</span><span> </span><span style="color:#b39f04;">print</span><span>(op, </span><span style="color:#5597d6;">end</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">''</span><span>)
</span></code></pre>
<p>By default, files passed as CLI arguments will be processed:</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> python3 inplace.py </span><span style="color:#72ab00;">*</span><span>.md
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> If you already know which inputs have to be processed, use the <code>files</code> argument. Use the <code>backup</code> argument if you want to make copies of original files in case something goes wrong. See my blog post <a href="https://www.python-engineer.com/posts/inplace-file-editing/">In-place file editing with fileinput module</a> for more details and examples.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/CGw_yf5XD6E" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook for a short, introductory guide for the Python programming language.</p>
Vim tip 4: reposition current line in Normal mode2022-02-09T00:00:00+00:002022-02-09T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-4/<p>You're likely to be familiar with commands to scroll through the contents like <kbd>Ctrl</kbd> followed by <kbd>d</kbd> or <kbd>f</kbd> or <kbd>u</kbd> or <kbd>b</kbd>.</p>
<p>Did you know that Vim also has handy options to keep the cursor on the current line while moving the contents around?</p>
<ul>
<li><kbd>zz</kbd> reposition the current line to the middle of the visible window
<ul>
<li>useful to see context around lines that are nearer to the top/bottom of the visible window</li>
</ul>
</li>
<li><kbd>zt</kbd> reposition the current line to the top of the visible window</li>
<li><kbd>zb</kbd> reposition the current line to the bottom of the visible window</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/options.txt.html#%27scrolloff%27">:h 'scrolloff'</a> option if you want to always show context around the current line.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/XRXO4Ns8rPE" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
PyDev of the Week2022-02-02T00:00:00+00:002022-02-02T00:00:00+00:00https://learnbyexample.github.io/mini/pydev-interview/<p>Last month I had the wonderful opportunity to be part of <a href="https://www.blog.pythonlibrary.org/category/pydevoftheweek/">PyDev of the Week</a> series organized by <strong>Michael Driscoll</strong>.</p>
<p>It was a pleasure to walk down the memory lane for this interview: <a href="https://www.blog.pythonlibrary.org/2022/01/31/pydev-of-the-week-sundeep-agarwal/">https://www.blog.pythonlibrary.org/2022/01/31/pydev-of-the-week-sundeep-agarwal/</a></p>
<p>I got to discuss about my education, career, writing ebooks and more. Hope you enjoy the interview!</p>
CLI tip 5: aligning columns2022-02-02T00:00:00+00:002022-06-17T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-5/<p>The <code>column</code> command is a nifty tool to align input data column wise. By default, whitespace is used as the input delimiter. Space character is used to align the output columns, so whitespace characters like tab will get converted to spaces.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'one two three\nfour five six\n'
</span><span>one two three
</span><span>four five six
</span><span>
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'one two three\nfour five six\n' </span><span style="color:#72ab00;">|</span><span> column </span><span style="color:#72ab00;">-</span><span>t
</span><span>one two three
</span><span>four five six
</span></code></pre>
<p>You can use the <code>-s</code> option to customize the input delimiter. Note that the output delimiter will still be made up of spaces only.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat scores.csv
</span><span style="color:#5597d6;">Name</span><span>,</span><span style="color:#5597d6;">Maths</span><span>,</span><span style="color:#5597d6;">Physics</span><span>,</span><span style="color:#5597d6;">Chemistry
</span><span style="color:#5597d6;">Ith</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">100</span><span>,</span><span style="color:#b3933a;">100
</span><span style="color:#5597d6;">Cy</span><span>,</span><span style="color:#b3933a;">97</span><span>,</span><span style="color:#b3933a;">98</span><span>,</span><span style="color:#b3933a;">95
</span><span style="color:#5597d6;">Lin</span><span>,</span><span style="color:#b3933a;">78</span><span>,</span><span style="color:#b3933a;">83</span><span>,</span><span style="color:#b3933a;">80
</span><span>
</span><span>$ column </span><span style="color:#72ab00;">-</span><span>s, </span><span style="color:#72ab00;">-</span><span>t scores.csv
</span><span style="color:#5597d6;">Name Maths Physics Chemistry
</span><span style="color:#5597d6;">Ith </span><span style="color:#b3933a;">100 100 100
</span><span style="color:#5597d6;">Cy </span><span style="color:#b3933a;">97 98 95
</span><span style="color:#5597d6;">Lin </span><span style="color:#b3933a;">78 83 80
</span><span>
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'1:-:2:-:3\napple:-:banana:-:cherry\n' </span><span style="color:#72ab00;">|</span><span> column </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">s:</span><span style="color:#72ab00;">-: -</span><span>t
</span><span style="color:#b3933a;">1 2 3
</span><span>apple banana cherry
</span></code></pre>
<p><img src="/images/warning.svg" alt="warning" /> Input should have a newline at the end, otherwise you'll get an error:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'1 2 3\na b c' </span><span style="color:#72ab00;">|</span><span> column </span><span style="color:#72ab00;">-</span><span>t
</span><span style="color:#b3933a;">column:</span><span> line too long
</span><span style="color:#b3933a;">1 2 3
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/0IrFBSjFgPQ" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/cli-computing">Linux Command Line Computing</a> ebook and <code>man column</code> for more details.</p>
Python tip 5: random choice and sample2022-01-25T00:00:00+00:002022-06-14T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-5/<p>Here are a couple of commonly used methods for the built-in <code>random</code> module:</p>
<ul>
<li><code>choice()</code> method helps you get a random element</li>
<li><code>sample()</code> method helps you get a <code>list</code> of a specific count of random elements</li>
</ul>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> import </span><span>random
</span><span style="color:#72ab00;">>>> </span><span>nums </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">4</span><span>, </span><span style="color:#b3933a;">5</span><span>, </span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#b3933a;">51</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">6</span><span>, </span><span style="color:#b3933a;">22</span><span>]
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>random.</span><span style="color:#5597d6;">choice</span><span>(nums)
</span><span style="color:#b3933a;">3
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>random.</span><span style="color:#5597d6;">sample</span><span>(nums, </span><span style="color:#5597d6;">k</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">4</span><span>)
</span><span>[</span><span style="color:#b3933a;">51</span><span>, </span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">1</span><span>]
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>random.</span><span style="color:#5597d6;">sample</span><span>(</span><span style="color:#b39f04;">range</span><span>(</span><span style="color:#b3933a;">1000</span><span>), </span><span style="color:#5597d6;">k</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">5</span><span>)
</span><span>[</span><span style="color:#b3933a;">490</span><span>, </span><span style="color:#b3933a;">26</span><span>, </span><span style="color:#b3933a;">9</span><span>, </span><span style="color:#b3933a;">745</span><span>, </span><span style="color:#b3933a;">919</span><span>]
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> Both these methods will work on any sequence object. The <code>sample()</code> method also accepts a <code>set</code> object, but that will be deprecated.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/KMEQ0zto3l4" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook for a short, introductory guide for the Python programming language.</p>
Brag post: Hacker News Front Page entries2022-01-21T00:00:00+00:002023-09-05T00:00:00+00:00https://learnbyexample.github.io/mini/hacker-news-front-page-brag/<p>In case you haven't yet read this nice post <a href="https://jvns.ca/blog/brag-documents/">"Get your work recognized: write a brag document"</a> by Julia Evans, please do that first.</p>
<p>I definitely found it nice to collect which of my content have reached Hacker News front page over the past 4 years. As I wrote in <a href="https://learnbyexample.github.io/my-book-writing-experience/">my book writing experience post</a>, the responses I got for my GNU awk one-liners collection was one of the stepping stones towards my career as a technical author.</p>
<p>Here's the list so far, ordered by oldest first:</p>
<ol>
<li><a href="https://news.ycombinator.com/item?id=15549318">Learn to use Awk with hundreds of examples</a> — <em>478 points, Oct 2017, 116 comments</em></li>
<li><a href="https://news.ycombinator.com/item?id=20212622">Show HN: I wrote a book on GNU grep and ripgrep</a> — <em>182 points, June 2019, 53 comments</em></li>
<li><a href="https://news.ycombinator.com/item?id=20645319">Show HN: I wrote a book on Python regular expressions</a> — <em>193 points, Aug 2019, 50 comments</em></li>
<li><a href="https://news.ycombinator.com/item?id=22758217">Show HN: An eBook with hundreds of GNU Awk one-liners</a> — <em>539 points, April 2020, 48 comments</em></li>
<li><a href="https://news.ycombinator.com/item?id=24637797">Show HN: Ruby One-Liners Cookbook</a> — <em>191 points, Sept 2020, 36 comments</em></li>
<li><a href="https://news.ycombinator.com/item?id=25006829">Perl One-Liners Cookbook</a> — <em>126 points, Nov 2020, 47 comments</em></li>
<li><a href="https://news.ycombinator.com/item?id=26076721">Show HN: "100 Page Python Intro" eBook</a> — <em>107 points, Feb 2021, 26 comments</em></li>
<li><a href="https://news.ycombinator.com/item?id=26356095">Paying my bills with 'free' ebooks</a> — <em>85 points, Mar 2021, 22 comments</em></li>
<li><a href="https://news.ycombinator.com/item?id=28798095">Show HN: "Command line text processing with GNU Coreutils" eBook</a> — <em>117 points, Oct 2021, 20 comments</em></li>
<li><a href="https://news.ycombinator.com/item?id=29391107">Show HN: Improve your Python regex skills with 75 interactive exercises</a> — <em>175 points, Nov 2021, 12 comments</em></li>
<li><a href="https://news.ycombinator.com/item?id=29837543">Vim prank: alias vim='vim -y'</a> — <em>341 points, Jan 2022, 259 comments</em></li>
<li><a href="https://news.ycombinator.com/item?id=30684232">Vim Reference Guide</a> — <em>244 points, Mar 2022, 110 comments</em></li>
<li><a href="https://news.ycombinator.com/item?id=33931677">Show HN: Interactive exercises for Linux CLI text processing commands</a> — <em>69 points, Dec 2022, 7 comments</em></li>
<li><a href="https://news.ycombinator.com/item?id=37290356">CLI text processing with GNU awk</a> — <em>419 points, Aug 2023, 129 comments</em></li>
</ol>
<p>As is the case with other social media platforms, being an active participant on Hacker News definitely helps. Apart from commenting on other topics, I also post links to projects and resources that I felt were useful. The number of such links reaching front page outnumbers my own content links.</p>
Removing duplicates irrespective of field order2022-01-19T00:00:00+00:002023-01-07T00:00:00+00:00https://learnbyexample.github.io/duplicates-irrespective-field-order/<p>I posted a coding challenge in the tenth issue of <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a>. I discuss the problem and various solutions in this blog post.</p>
<span id="continue-reading"></span><br>
<h2 id="problem-statement">Problem statement<a class="zola-anchor" href="#problem-statement" aria-label="Anchor link for: problem-statement">🔗</a></h2>
<p>Retain only the first copy of duplicate lines irrespective of the order of the fields. Input order should be maintained. Assume space as the field separator with exactly two fields on each line. For example, <code>hehe haha</code> and <code>haha hehe</code> will be considered as duplicates.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ cat twos.txt
</span><span>hehe haha
</span><span>door floor
</span><span>haha hehe
</span><span>6;8 3-4
</span><span>true blue
</span><span>hehe bebe
</span><span>floor door
</span><span>3-4 6;8
</span><span>tru eblue
</span><span>haha hehe
</span></code></pre>
<p><strong>Expected output for the above sample:</strong></p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>hehe haha
</span><span>door floor
</span><span>6;8 3-4
</span><span>true blue
</span><span>hehe bebe
</span><span>tru eblue
</span></code></pre>
<br>
<h2 id="python-solution">Python solution<a class="zola-anchor" href="#python-solution" aria-label="Anchor link for: python-solution">🔗</a></h2>
<p>Here's one possible solution for this problem:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span>filename </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'twos.txt'
</span><span>keys </span><span style="color:#72ab00;">= </span><span style="color:#a2a001;">set</span><span>()
</span><span>
</span><span style="color:#72ab00;">with </span><span style="color:#b39f04;">open</span><span>(filename) </span><span style="color:#72ab00;">as </span><span>f:
</span><span> </span><span style="color:#72ab00;">for </span><span>line </span><span style="color:#72ab00;">in </span><span>f:
</span><span> fields </span><span style="color:#72ab00;">= </span><span>line.</span><span style="color:#5597d6;">split</span><span>()
</span><span> key1 </span><span style="color:#72ab00;">= </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{fields[</span><span style="color:#b3933a;">0</span><span>]} {fields[</span><span style="color:#b3933a;">1</span><span>]}</span><span style="color:#d07711;">'
</span><span> key2 </span><span style="color:#72ab00;">= </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{fields[</span><span style="color:#b3933a;">1</span><span>]} {fields[</span><span style="color:#b3933a;">0</span><span>]}</span><span style="color:#d07711;">'
</span><span> </span><span style="color:#72ab00;">if not </span><span>(key1 </span><span style="color:#72ab00;">in </span><span>keys </span><span style="color:#72ab00;">or </span><span>key2 </span><span style="color:#72ab00;">in </span><span>keys):
</span><span> </span><span style="color:#b39f04;">print</span><span>(line, </span><span style="color:#5597d6;">end</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">''</span><span>)
</span><span> keys.</span><span style="color:#5597d6;">add</span><span>(key1)
</span></code></pre>
<p>The main trick in the above solution is to check the input field order as well as the reversed order against elements in a set. A subtle point to note is that the <code>split()</code> string method also removes whitespaces from the start and end of the input line. If you had to use another field delimiter (for example, comma) you'll have to remove the line ending before splitting the input.</p>
<p>And here's a generic solution for any number of fields, which also makes the solution look simpler:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span>filename </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'twos.txt'
</span><span>keys </span><span style="color:#72ab00;">= </span><span style="color:#a2a001;">set</span><span>()
</span><span>
</span><span style="color:#72ab00;">with </span><span style="color:#b39f04;">open</span><span>(filename) </span><span style="color:#72ab00;">as </span><span>f:
</span><span> </span><span style="color:#72ab00;">for </span><span>line </span><span style="color:#72ab00;">in </span><span>f:
</span><span> fields </span><span style="color:#72ab00;">= </span><span>line.</span><span style="color:#5597d6;">split</span><span>()
</span><span> sorted_key </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">' '</span><span>.</span><span style="color:#5597d6;">join</span><span>(</span><span style="color:#b39f04;">sorted</span><span>(fields))
</span><span> </span><span style="color:#72ab00;">if </span><span>sorted_key </span><span style="color:#72ab00;">not in </span><span>keys:
</span><span> </span><span style="color:#b39f04;">print</span><span>(line, </span><span style="color:#5597d6;">end</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">''</span><span>)
</span><span> keys.</span><span style="color:#5597d6;">add</span><span>(sorted_key)
</span></code></pre>
<p>In case you are wondering why space is used to join the field contents, it is necessary to avoid false matches. <code>tru eblue</code> shouldn't be considered as a duplicate of <code>true blue</code> or <code>blue true</code>. Space is a safe character to use since it is the field separator.</p>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook if you already know programming basics but new to Python.</p>
<br>
<h2 id="gnu-awk-one-liner">GNU awk one-liner<a class="zola-anchor" href="#gnu-awk-one-liner" aria-label="Anchor link for: gnu-awk-one-liner">🔗</a></h2>
<p>Here's a solution for CLI enthusiasts:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span>$ awk </span><span style="color:#d07711;">'!(($1,$2) in seen || ($2,$1) in seen); {seen[$1,$2]}' </span><span>twos.txt
</span><span>hehe haha
</span><span>door floor
</span><span style="color:#b3933a;">6</span><span>;</span><span style="color:#b3933a;">8 3</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">4
</span><span>true blue
</span><span>hehe bebe
</span><span>tru eblue
</span></code></pre>
<p>The above solution is similar to the first Python solution with a notable difference. The fields are joined using <code>\034</code> (a non-printing character), which is usually not present in text files.</p>
<p>A solution using the field separator instead of <code>\034</code> would look like:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>awk </span><span style="color:#d07711;">'!(($1 FS $2) in seen || ($2 FS $1) in seen); {seen[$1 FS $2]}'
</span></code></pre>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/learn_gnuawk">CLI text processing with GNU awk</a> ebook if you are interested in such one-liners.</p>
Vim tip 3: autocomplete words and lines in Insert mode2022-01-18T00:00:00+00:002022-01-18T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-3/<p><strong>Autocomplete word</strong></p>
<ul>
<li><kbd>Ctrl</kbd>+<kbd>p</kbd> autocomplete word based on matching words in the backward direction</li>
<li><kbd>Ctrl</kbd>+<kbd>n</kbd> autocomplete word based on matching words in the forward direction</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> If more than one word matches, they are displayed using a popup menu. You can use <kbd>↑</kbd>/<kbd>↓</kbd> arrow keys or <kbd>Ctrl</kbd>+<kbd>p</kbd>/<kbd>Ctrl</kbd>+<kbd>n</kbd> to move through this list.</p>
<p><img src="/images/info.svg" alt="info" /> With multiple matches, you'll notice that the first match is automatically inserted and moving through the list doesn't change the text that was inserted. You'll have to press <kbd>Ctrl</kbd>+<kbd>y</kbd> or <kbd>Enter</kbd> key to choose a different completion text. If you were satisfied with the first match, typing any character will make the popup menu disappear and insert whatever character you had typed. Or press <kbd>Esc</kbd> to select the first match and go to Normal mode.</p>
<p><strong>Autocomplete line</strong></p>
<ul>
<li><kbd>Ctrl</kbd>+<kbd>x</kbd> followed by <kbd>Ctrl</kbd>+<kbd>l</kbd> autocomplete line based on matching lines in the backward direction</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> If more than one line matches, they are displayed using a popup menu. You can use <kbd>↑</kbd>/<kbd>↓</kbd> arrow keys or <kbd>Ctrl</kbd>+<kbd>p</kbd>/<kbd>Ctrl</kbd>+<kbd>n</kbd> to move through this list. You can also use <kbd>Ctrl</kbd>+<kbd>l</kbd> to move up the list.</p>
<p><strong>Autocomplete assist</strong></p>
<ul>
<li><kbd>Ctrl</kbd>+<kbd>e</kbd> cancels autocomplete
<ul>
<li>you'll retain the text you had typed before invoking autocomplete</li>
</ul>
</li>
<li><kbd>Ctrl</kbd>+<kbd>y</kbd> or <kbd>Enter</kbd> change the autocompletion text to the currently selected item from the popup menu</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/insert.txt.html#ins-completion">:h ins-completion</a> for more details and other autocomplete features.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/GJrsWOtcmqM" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
Regexp gotcha 1: grouping common portions2022-01-14T00:00:00+00:002022-05-13T00:00:00+00:00https://learnbyexample.github.io/mini/regexp-gotcha-1/<p>Similar to <code>a(b+c)d = abd+acd</code> in maths, you get <code>a(b|c)d = abd|acd</code> in regular expressions. However, you'll have to be careful if quantifiers are involved.</p>
<p>For example, <code>(a*|b*)</code> isn't the same as <code>(a|b)*</code>. Can you reason out why? Here's a railroad diagram to help you out:</p>
<p align="center"><img src="/images/mini/regexp_gotcha_1.png" alt="Regexp grouping with quantifiers gotcha" /></p>
<p align="center">Credit: <a href="https://www.debuggex.com/">debuggex.com</a></p>
<p>The difference is that <code>(a*|b*)</code> only matches same letter sequences like <code>a</code>, <code>bb</code>, <code>aaaaaa</code>, etc. But <code>(a|b)*</code> can match mixed sequences like <code>ababbba</code> too. You can also simplify <code>(a|b)*</code> to <code>[ab]*</code> since it is just single character alternation in this particular example.</p>
<p>Here's an illustration using Python:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> import </span><span>re
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>test </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'aa'</span><span>, </span><span style="color:#d07711;">'abbaba'</span><span>, </span><span style="color:#d07711;">'aaabbb'</span><span>, </span><span style="color:#d07711;">'bbbbb'</span><span>, </span><span style="color:#d07711;">'abc'</span><span>]
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>[s </span><span style="color:#72ab00;">for </span><span>s </span><span style="color:#72ab00;">in </span><span>test </span><span style="color:#72ab00;">if </span><span>re.</span><span style="color:#5597d6;">fullmatch</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(a</span><span style="color:#72ab00;">*|</span><span style="color:#7c8f4c;">b</span><span style="color:#72ab00;">*</span><span style="color:#7c8f4c;">)</span><span style="color:#d07711;">'</span><span>, s)]
</span><span>[</span><span style="color:#d07711;">'aa'</span><span>, </span><span style="color:#d07711;">'bbbbb'</span><span>]
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>[s </span><span style="color:#72ab00;">for </span><span>s </span><span style="color:#72ab00;">in </span><span>test </span><span style="color:#72ab00;">if </span><span>re.</span><span style="color:#5597d6;">fullmatch</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#7c8f4c;">(a</span><span style="color:#72ab00;">|</span><span style="color:#7c8f4c;">b)</span><span style="color:#72ab00;">*</span><span style="color:#d07711;">'</span><span>, s)]
</span><span>[</span><span style="color:#d07711;">'aa'</span><span>, </span><span style="color:#d07711;">'abbaba'</span><span>, </span><span style="color:#d07711;">'aaabbb'</span><span>, </span><span style="color:#d07711;">'bbbbb'</span><span>]
</span></code></pre>
<br>
<p><img src="/images/info.svg" alt="info" /> Want to learn regular expressions from the basics with plenty of examples and exercises? I've written <a href="https://learnbyexample.github.io/books">regexp ebooks for Python, JavaScript, Ruby and CLI tools</a>.</p>
CLI tip 4: serialize file contents to a single line2022-01-12T00:00:00+00:002022-06-14T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-4/<p>The <code>-s</code> option is one of the useful, but lesser known feature of the <code>paste</code> command. It helps you to serialize input file contents to a single output line.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat colors.txt
</span><span>blue
</span><span>white
</span><span>orange
</span><span>
</span><span>$ paste </span><span style="color:#72ab00;">-</span><span>sd, colors.txt
</span><span>blue,white,orange
</span></code></pre>
<p>If multiple files are passed, serialization of each file is displayed on separate output lines.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ paste </span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">sd: </span><span style="color:#72ab00;"><</span><span>(seq </span><span style="color:#b3933a;">3</span><span>) </span><span style="color:#72ab00;"><</span><span>(seq </span><span style="color:#b3933a;">5 9</span><span>)
</span><span style="color:#b3933a;">1</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">2</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">3
</span><span style="color:#b3933a;">5</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">6</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">7</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">8</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">9
</span></code></pre>
<p>The advantage of using <code>paste</code> instead of other options like <code>tr</code>, <code>awk</code>, etc is that you do not have to worry about trailing delimiters, newlines, etc. For example:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># issue 1: trailing comma
</span><span style="color:#7f8989;"># issue 2: no newline at the end
</span><span>$ </span><span style="color:#72ab00;"><</span><span>colors.txt tr </span><span style="color:#d07711;">'\n' ','
</span><span>blue,white,orange,
</span><span>
</span><span style="color:#7f8989;"># correcting the above two issues
</span><span>$ </span><span style="color:#72ab00;"><</span><span>colors.txt tr </span><span style="color:#d07711;">'\n' ',' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/,$/\n/'
</span><span>blue,white,orange
</span></code></pre>
<p>Here's an equivalent <code>awk</code> solution for single file input. While slower and complicated compared to the <code>paste</code> solution, you get more flexibility since <code>awk</code> is a programming language. For example, it is pretty easy to use multicharacter output delimiter.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ awk </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">ORS</span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'NR>1{print ","} 1; END{print "\n"}'</span><span> colors.txt
</span><span>blue,white,orange
</span><span>
</span><span>$ awk </span><span style="color:#72ab00;">-</span><span>v </span><span style="color:#c23f31;">ORS</span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'NR>1{print " : "} 1; END{print "\n"}'</span><span> colors.txt
</span><span>blue </span><span style="color:#72ab00;">:</span><span> white </span><span style="color:#72ab00;">:</span><span> orange
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/NX91QlHWAio" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://learnbyexample.github.io/cli_text_processing_coreutils/paste.html">paste command</a> chapter from my <a href="https://github.com/learnbyexample/cli_text_processing_coreutils">Command line text processing with GNU Coreutils</a> ebook for more details.</p>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/learn_gnuawk">CLI text processing with GNU awk</a> ebook if you are interested in learning about the <code>awk</code> command.</p>
Automating Excel with Python - book review2022-01-11T00:00:00+00:002022-12-21T00:00:00+00:00https://learnbyexample.github.io/automating-excel-with-python-review/<p>In this post, I review <strong>Automating Excel with Python</strong> by <a href="https://www.blog.pythonlibrary.org/">Michael Driscoll</a>. From the introduction chapter of this book:</p>
<blockquote>
<p>The purpose of this book is to help you learn how to use Python to work with Excel. You will be using a package called OpenPyXL to create, read, and edit Excel documents with Python. While the focus of this book will be on OpenPyXL, you will also learn about other Python packages that you can use to interact with Excel using the Python programming language.</p>
</blockquote>
<span id="continue-reading"></span><h2 id="book-details">Book details<a class="zola-anchor" href="#book-details" aria-label="Anchor link for: book-details">🔗</a></h2>
<p align="center"><img src="/images/automating_excel/automating_excel_with_python.jpg" alt="Automating Excel with Python book cover" /></p>
<p align="center">Book cover</p>
<ul>
<li><a href="https://www.amazon.com/dp/B09M5551W2">Amazon</a> — Paperback, Kindle</li>
<li><a href="https://driscollis.gumroad.com/l/openpyxl">Gumroad</a> — PDF, EPUB, Mobi</li>
<li><a href="https://leanpub.com/openpyxl">Leanpub</a> — PDF, EPUB, Mobi</li>
<li><a href="https://github.com/driscollis/automating_excel_with_python">GitHub</a> — code examples and sample spreadsheets used in the book</li>
<li><a href="https://www.goodreads.com/book/show/59974445-automating-excel-with-python">Goodreads</a> — book reviews</li>
</ul>
<h2 id="review">Review<a class="zola-anchor" href="#review" aria-label="Anchor link for: review">🔗</a></h2>
<p>My very first job assignment (at a semiconductor company) required me to use spreadsheets for tabulating results of various experiments, adding charts, etc. I used to manually copy-paste the results generated from a Perl script. There were multiple sheets and my work was complicated enough to require multiple months of refinement, feature modifications, etc. Not sure if a library like OpenPyXL existed back then, but I think I should've at least asked/searched ways to automate the spreadsheet process.</p>
<p>Going through this book felt like someone wrote a book just for that project, albeit 13 years late. Here's a rough list of features that would've helped me:</p>
<ul>
<li>Creating <code>xlsx</code> files with multiple sheets</li>
<li>Adding data</li>
<li>Formatting cells based on a known equation</li>
<li>Creating charts</li>
</ul>
<p>Instructions and examples were clear and easy to follow. Snapshots were also shown for all the examples, so you can check if you've followed along as expected. While the book is best suited if you have MS Excel, most of the examples worked for me on LibreOffice Calc. Only the charts had major differences — some types weren't supported and x/y axis label/data were problematic as shown below:</p>
<p align="center"><img src="/images/automating_excel/bar_chart_excel.png" alt="Bar Chart in Excel" width="600px" height="400px"/></p>
<p align="center">Bar Chart in Excel (snapshot from the book)</p>
<p align="center"><img src="/images/automating_excel/bar_chart_calc.png" alt="Bar Chart in Calc" width="600px" height="400px"/></p>
<p align="center">Bar Chart in LibreOffice Calc (what I got on my machine)</p>
<br>
<p>Apart from the <code>openpyxl</code> module, the author also briefly covered how you can use <code>pandas</code>, <code>xlsxwriter</code> and <code>gspread</code> (for working with Google sheets). Some features were presented at the end as Appendix chapters.</p>
<h2 id="table-of-contents">Table of Contents<a class="zola-anchor" href="#table-of-contents" aria-label="Anchor link for: table-of-contents">🔗</a></h2>
<ul>
<li>Introduction</li>
<li>Chapter 1 - Setting Up Your Machine</li>
<li>Chapter 2 - Reading Spreadsheets with OpenPyXL</li>
<li>Chapter 3 - Creating a Spreadsheet with OpenPyXL</li>
<li>Chapter 4 - Styling Cells</li>
<li>Chapter 5 - Conditional Formatting</li>
<li>Chapter 6 - Creating Charts</li>
<li>Chapter 7 - Chart Types</li>
<li>Chapter 8 - Converting CSV to Excel</li>
<li>Chapter 9 - Using Pandas with Excel</li>
<li>Chapter 10 - Python and Google Sheets</li>
<li>Chapter 11 - XlsxWriter</li>
<li>Appendix A - Cell Comments</li>
<li>Appendix B - Print Settings Basics</li>
<li>Appendix C - Formulas</li>
</ul>
<h2 id="feedback-and-reviews">Feedback and Reviews<a class="zola-anchor" href="#feedback-and-reviews" aria-label="Anchor link for: feedback-and-reviews">🔗</a></h2>
<p>All in all, I would highly recommend this book for those wanting to use Python for automating spreadsheets. I'd request you to post reviews after going through the book (they help us indie authors a lot). And please do contact the author to let him know your feedback or if you have any clarifications.</p>
<p>Happy learning :)</p>
Vim prank: alias vim='vim -y'2022-01-07T00:00:00+00:002022-08-17T00:00:00+00:00https://learnbyexample.github.io/mini/vim-prank/<p align="center"><img src="/images/vim_prank.png" alt="Vim Prank" /></p>
<p align="center"><i>Poster created using <a href="https://www.canva.com/">Canva</a></i></p><br>
<p>While going through <a href="https://vimhelp.org/starting.txt.html#vim-arguments">:h vim-arguments</a> for my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> ebook, I came across the <code>-y</code> option:</p>
<blockquote>
<p>Easy mode. Implied for <code>evim</code> and <code>eview</code>. Starts with <code>'insertmode'</code> set and behaves like a click-and-type editor. This sources the script $VIMRUNTIME/evim.vim. Mappings are set up to work like most click-and-type editors, see <code>evim-keys</code>. The GUI is started when available.</p>
</blockquote>
<p>It was so weird to use. Copy and paste works with <code>Ctrl+c</code> and <code>Ctrl+v</code> respectively. Text can be selected with mouse and typing new text overwrites this selected portion. <code>Esc</code> key doesn't work (gasp!), so I couldn't quit until I used the window buttons. Later I tried and found that <code>Ctrl+o</code> works, which would then allow you to use <code>:q</code> as usual.</p>
<p>So, if you want to prank a Vim user:</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#b39f04;">alias </span><span style="color:#c23f31;">vim</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'vim -y'
</span></code></pre>
<br>
<p><img src="/images/info.svg" alt="info" /> I didn't expect such a good response on <a href="https://www.reddit.com/r/vim/comments/rxedpj/vim_prank_alias_vimvim_y/">/r/vim/</a> and <a href="https://twitter.com/learn_byexample/status/1478976141650059264">twitter</a> for this "easy" feature. So, decided to write this mini blog post as well. Also, I got to know a few more ways to escape this madness from the /r/vim/ sub:</p>
<blockquote>
<p>One hint: If you want to go to Normal mode to be able to type a sequence of commands, use <code>CTRL-L</code>. <a href="https://vimhelp.org/starting.txt.html#evim-keys">https://vimhelp.org/starting.txt.html#evim-keys</a></p>
</blockquote>
<blockquote>
<p>Use <code><c-\><c-n></code> See <code>:h CTRL-\_CTRL-N</code></p>
</blockquote>
<br>
<p><strong>Update</strong></p>
<p>So, this post reached front page on <a href="https://news.ycombinator.com/item?id=29837543">Hacker News</a>! Plenty of interesting comments and got to know about <a href="https://github.com/tombh/novim-mode">novim-mode</a> plugin (which aims to make Vim behave more like a 'normal' editor).</p>
<p>I also found <a href="https://www.reddit.com/r/vim/comments/5102o5/an_evil_trick_to_prank_vimsters/">an old discussion on /r/vim/</a> discussing ways to trick a Vim user.</p>
Python tip 4: comparison chaining2022-01-04T00:00:00+00:002022-06-10T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-4/<p>You can chain comparison operators arbitrarily. Apart from terser code, this also has the advantage of having to evaluate the middle expression only once.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> from </span><span>math </span><span style="color:#72ab00;">import </span><span>factorial
</span><span>
</span><span style="color:#7f8989;"># factorial function gets called twice for this example
</span><span style="color:#72ab00;">>>> </span><span style="color:#5597d6;">factorial</span><span>(</span><span style="color:#b3933a;">3</span><span>) </span><span style="color:#72ab00;">> </span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;">and </span><span style="color:#5597d6;">factorial</span><span>(</span><span style="color:#b3933a;">3</span><span>) </span><span style="color:#72ab00;">< </span><span style="color:#b3933a;">10
</span><span style="color:#b3933a;">True
</span><span>
</span><span style="color:#7f8989;"># function needs to be called only once here
</span><span style="color:#72ab00;">>>> </span><span style="color:#b3933a;">2 </span><span style="color:#72ab00;">< </span><span style="color:#5597d6;">factorial</span><span>(</span><span style="color:#b3933a;">3</span><span>) </span><span style="color:#72ab00;">< </span><span style="color:#b3933a;">10
</span><span style="color:#b3933a;">True
</span><span>
</span><span style="color:#7f8989;"># another example
</span><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">'bat' </span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'cat' </span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'cater'
</span><span style="color:#b3933a;">True
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/kmthqcfKPGg" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook for a short, introductory guide for the Python programming language.</p>
2021 was a wild ride2021-12-30T00:00:00+00:002022-12-21T00:00:00+00:00https://learnbyexample.github.io/wild-ride-2021/<p><strong>TL;DR</strong>: Started and ended the year well, with a depressing period in the middle. Published three programming ebooks, several blog posts, started a newsletter, improved Twitter readership, read 80+ novels, and so on. Had a good year in terms of ebook sales 😇</p>
<span id="continue-reading"></span><br>
<h2 id="books-published">Books published<a class="zola-anchor" href="#books-published" aria-label="Anchor link for: books-published">🔗</a></h2>
<ul>
<li><a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> — short, introductory guide for the Python programming language. Started writing last year, published in February</li>
<li><a href="https://github.com/learnbyexample/practice_python_projects">Practice Python Projects</a> — five beginner to intermediate level projects inspired by real world use cases. Started writing last year (before "100 Page Python Intro"!), published in July</li>
<li><a href="https://github.com/learnbyexample/cli_text_processing_coreutils">Command line text processing with GNU Coreutils</a> — learn 20+ specialized text processing tools provided by the GNU coreutils package. Published in October</li>
</ul>
<p>I also spent time updating all my existing books from February to May.</p>
<p align="center"><img src="/images/books/2021_books.png" alt="Programming books published in 2021" /></p>
<br>
<h2 id="workshops">Workshops<a class="zola-anchor" href="#workshops" aria-label="Anchor link for: workshops">🔗</a></h2>
<p>First and only workshop I conducted since the start of pandemic in 2020. And this was possible only because it was online. The topic was Python scripting introduction for Biotech students. Publishing "100 Page Python Intro" was timely for this workshop.</p>
<p>This took up most of my time during March/April along with updating existing books.</p>
<br>
<h2 id="blog-posts">Blog posts<a class="zola-anchor" href="#blog-posts" aria-label="Anchor link for: blog-posts">🔗</a></h2>
<p>I've been consistently writing books for the past three years, but I find it difficult to come up with ideas for my programming blog. This is partly due to not wanting to repeat content from my books. Here's my favorite posts I wrote this year:</p>
<ul>
<li><a href="https://learnbyexample.github.io/my-book-writing-experience/">Paying my bills with 'free' ebooks</a></li>
<li><a href="https://learnbyexample.github.io/gnu-bre-ere-cheatsheet/">GNU BRE/ERE cheatsheet and differences between grep, sed and awk</a></li>
<li><a href="https://learnbyexample.github.io/escaping-madness-awk-literal-field-separator/">Escaping madness to get literal field separators in awk</a></li>
<li><a href="https://www.perl.com/article/perl-one-liners-part-1/">Perl / Unix One-liner Cage Match, Part 1</a> and <a href="https://www.perl.com/article/perl-one-liners-part-2/">Perl / Unix One-liner Cage Match, Part 2</a></li>
</ul>
<p>I tried to be more consistent by posting short articles (see <a href="https://learnbyexample.github.io/mini/">mini posts list</a>), but lost interest. Starting a newsletter in November helped change my perspective about re-using content from my books. I've started posting tips and coding challenges that are short and easy to digest:</p>
<ul>
<li><a href="https://learnbyexample.github.io/tips/">Programming tips</a></li>
<li><a href="https://learnbyexample.github.io/numeric-palindrome/">Numeric Palindrome</a></li>
<li><a href="https://learnbyexample.github.io/counting-nested-braces/">Counting nested braces</a></li>
</ul>
<p>I was more consistent for my <a href="https://learnbyexample.github.io/escapist-reviews/">Escapist Reviews</a> blog that I started late last year to review novels I read.</p>
<br>
<h2 id="book-sales">Book sales<a class="zola-anchor" href="#book-sales" aria-label="Anchor link for: book-sales">🔗</a></h2>
<p>Had better sales compared to last year, which I really wasn't expecting. Especially when the average monthly sales was around $100 between May to September (my monthly expenses is around $150). This coincided with some health issues and the struggle to finish writing the "Practice Python Projects" book.</p>
<p>This led me to reading articles about better landing pages, building audience on social media, affiliates, etc. I still have a long way to go, but I feel these active efforts led to much improved sales in the last quarter of the year. I ended up deciding not to use affiliates though.</p>
<p>Here's my sales chart from Gumroad for this year (I had similar revenue from Leanpub):</p>
<p align="center"><img src="/images/books/gumroad_sales_2021.png" alt="Gumroad sales in 2021" /></p>
<p>There were plenty of reasons that led to the awesome last quarter sales. Here's some significant events I remember:</p>
<ul>
<li>Joined hands with fellow Python authors for <a href="https://learnbyexample.github.io/indie-python-extravaganza/">The Indie Python Extravaganza</a> bundle (given away freely for a month)
<ul>
<li>A Twitter discussion led to the giveaway idea, which resulted in creating this bundle</li>
<li>Combined marketing efforts by all four of us gave significant paid sales too</li>
</ul>
</li>
<li>Published "Command line text processing with GNU Coreutils"
<ul>
<li>In addition to my usual practice of making a new book free, this time I offered <a href="https://learnbyexample.gumroad.com/l/all-books">All books bundle</a> for $5 and a lot of users bought it</li>
<li>Announcing the book on Reddit and Hacker News was well received</li>
<li>I was beginning to improve my Twitter audience around that time, which helped a bit</li>
<li>Got featured in Leanpub's monthly sales newsletter</li>
<li>Jesse Smith on <a href="https://distrowatch.com/weekly.php?issue=20211206#book">distrowatch.com</a> wrote a lovely book review, which resulted in significant sales in December</li>
</ul>
</li>
<li>Getting featured on <a href="https://rubyweekly.com/issues/574">Ruby weekly</a></li>
<li><a href="https://learnbyexample.github.io/programming-deals/">Programming deals</a> for the last week of November
<ul>
<li>Helped a lot by commenting on Hacker News and getting featured in blog posts of fellow Python authors</li>
</ul>
</li>
<li><a href="https://learnbyexample.github.io/python-25-days-of-regex/">Interactive GUI app for Python regex</a>
<ul>
<li>As part of 50 days of break from book writing, I worked on this Python app</li>
<li>Made it to the front page of Hacker News yet again</li>
</ul>
</li>
<li>"The Indie Python Extravaganza" bundle and some of my other books were featured in Leanpub's Boxing day sales</li>
<li>And I believe creating <a href="https://github.com/learnbyexample">GitHub Readme</a> helped as well</li>
</ul>
<p>The biggest takeaway for me was to actively look for opportunities (small or big) instead of just relying on <em>free</em> offering during book launch (which is about once in four months).</p>
<br>
<h2 id="newsletter">Newsletter<a class="zola-anchor" href="#newsletter" aria-label="Anchor link for: newsletter">🔗</a></h2>
<p>During the 50 days break, the other significant project I started was <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> newsletter. This is still in early stages to point out any impact it will have on my book sales, but it certainly has been a pleasure so far to email an issue every Friday.</p>
<p>And as mentioned earlier, this led me to write programming blog posts consistently (tips and coding challenges).</p>
<br>
<h2 id="building-twitter-audience">Building Twitter audience<a class="zola-anchor" href="#building-twitter-audience" aria-label="Anchor link for: building-twitter-audience">🔗</a></h2>
<p>I joined Twitter in 2015. My follower count was less than 400 in July. In December, I crossed 1100. This is far from being impressive (I know a few authors who added more than 15000 followers during that time period).</p>
<p>Being active on Twitter led me to awesome opportunities mentioned earlier in the Book sales section. The best tips I can give is to tweet consistently, interact with your readers and don't be afraid to participate in conversations initiated by top users. Oh, and reading articles/books about social media audience building would help too.</p>
<p><a href="https://twitter.com/learn_byexample">Follow me on Twitter</a> for interesting tech nuggets 😉</p>
<br>
<h2 id="fictional-reading">Fictional reading<a class="zola-anchor" href="#fictional-reading" aria-label="Anchor link for: fictional-reading">🔗</a></h2>
<p>I enjoy reading fantasy and science-fiction novels. I read 80+ SFF books this year and recently wrote a post listing <a href="https://learnbyexample.github.io/escapist-reviews/lists/2021-favorite-sff-novels/">my top 10 favorites</a>.</p>
<p>I also got a chance to beta read <a href="https://www.goodreads.com/book/show/57289544-the-siege-of-skyhold">The Siege of Skyhold</a> and an ARC of <a href="https://www.goodreads.com/book/show/58656291-bastion">Bastion</a>. I find these a good way to give back to the writing community, having myself received plenty of support from strangers.</p>
<br>
<h2 id="goals-for-2022">Goals for 2022<a class="zola-anchor" href="#goals-for-2022" aria-label="Anchor link for: goals-for-2022">🔗</a></h2>
<p>Foremost goal is to continue taking care of physical/mental health. And I'd be more than happy if I manage yet another year with $250+ average monthly income.</p>
<p>Books:</p>
<ul>
<li>I'm currently working on <a href="https://learnbyexample.github.io/vim_reference/">Vim Reference Guide</a> ebook. Likely to publish in the first quarter</li>
<li>I started working on <a href="https://learnbyexample.github.io/cli_text_processing_rust/">Command line text processing with Rust tools</a> ebook even before "Command line text processing with GNU Coreutils", hope to publish in 2022</li>
<li>Have several more book topics in mind, but not sure if I'll start working on any of them. And it is possible that I'll come up with something else I fancy and work on it instead of already planned topics</li>
</ul>
<p>Projects:</p>
<ul>
<li>Interactive apps for exercises from other books, similar to the one I did for Python regex</li>
<li>Games for fun</li>
</ul>
<p>Miscellaneous:</p>
<ul>
<li>Continue to build an audience via Twitter, Newsletter, etc</li>
<li>Contribute to other open source projects</li>
</ul>
<br>
<p>Here's wishing you a very happy, healthy and prosperous 2022 👍 😇</p>
Vim tip 2: indent/unindent lines2021-12-29T00:00:00+00:002021-12-29T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-2/<p><strong>Normal mode</strong></p>
<ul>
<li><kbd>>></kbd> indent the current line</li>
<li><kbd>3>></kbd> indent the current line and two lines below (same as <kbd>2>j</kbd>)</li>
<li><kbd>>k</kbd> indent the current line and the line above (same as <kbd>1>k</kbd> or <kbd>>1k</kbd>)</li>
<li><kbd><<</kbd> unindent the current line</li>
<li><kbd>5<<</kbd> unindent the current line and four lines below (same as <kbd>4<j</kbd> or <kbd><4j</kbd>)</li>
<li><kbd>2<k</kbd> unindent the current line and two lines above (same as <kbd><2k</kbd>)</li>
<li><kbd>=</kbd> auto indent code, use motion commands to indicate the portion to be indented
<ul>
<li><kbd>=4j</kbd> auto indents the current line and four lines below</li>
<li><kbd>=ip</kbd> auto indents the current paragraph</li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> You can use any motion command with <kbd>></kbd> and <kbd><</kbd>. For example, <kbd>>}</kbd> indents till the end of the paragraph.</p>
<p><strong>Visual mode</strong></p>
<ul>
<li><kbd>></kbd> indent the visually selected lines once</li>
<li><kbd>3></kbd> indent the visually selected lines three times</li>
<li><kbd><</kbd> unindent the visually selected lines once</li>
<li><kbd>=</kbd> auto indent code</li>
</ul>
<p>Consider the following unindented code:</p>
<pre data-lang="c" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-c "><code class="language-c" data-lang="c"><span style="color:#72ab00;">for</span><span>(i</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">1</span><span>; i</span><span style="color:#72ab00;"><</span><span style="color:#b3933a;">5</span><span>; i</span><span style="color:#72ab00;">++</span><span>)
</span><span>{
</span><span style="color:#72ab00;">for</span><span>(j</span><span style="color:#72ab00;">=</span><span>i; j</span><span style="color:#72ab00;"><</span><span style="color:#b3933a;">10</span><span>; j</span><span style="color:#72ab00;">++</span><span>)
</span><span>{
</span><span>statements
</span><span>}
</span><span>statements
</span><span>}
</span></code></pre>
<p>Here's the result after applying <kbd>vip=</kbd> (you can also use <kbd>=ip</kbd> if you prefer Normal mode).</p>
<pre data-lang="c" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-c "><code class="language-c" data-lang="c"><span style="color:#72ab00;">for</span><span>(i</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">1</span><span>; i</span><span style="color:#72ab00;"><</span><span style="color:#b3933a;">5</span><span>; i</span><span style="color:#72ab00;">++</span><span>)
</span><span>{
</span><span> </span><span style="color:#72ab00;">for</span><span>(j</span><span style="color:#72ab00;">=</span><span>i; j</span><span style="color:#72ab00;"><</span><span style="color:#b3933a;">10</span><span>; j</span><span style="color:#72ab00;">++</span><span>)
</span><span> {
</span><span> statements
</span><span> }
</span><span> statements
</span><span>}
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> Indentation depends on the <code>shiftwidth</code> setting.</p>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/change.txt.html#shift-left-right">:h shift-left-right</a>, <a href="https://vimhelp.org/change.txt.html#%3D">:h =</a> and <a href="https://vimhelp.org/options.txt.html#%27shiftwidth%27">:h 'shiftwidth'</a> for documentation.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/HuySlxcoAIU" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a> and <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a>.</p>
CLI tip 3: place backups in another directory with GNU sed2021-12-21T00:00:00+00:002022-06-14T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-3/<p>You can use <code>*</code> to place backups of original files in another directory when using the <code>-i</code> option with <code>GNU sed</code>. Consider these two sample input files in the current directory:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat f1.txt
</span><span>good morning
</span><span>that was good, just too good!
</span><span>$ cat f2.txt
</span><span>goodie goodbye
</span></code></pre>
<p>Create a <code>backups</code> directory and use <code>*</code> under this directory as a placeholder for the filenames passed to the <code>sed</code> command.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ mkdir backups
</span><span>$ sed </span><span style="color:#72ab00;">-</span><span>i</span><span style="color:#d07711;">'backups/*' 's/good/nice/'</span><span> f1.txt f2.txt
</span><span>$ ls backups</span><span style="color:#72ab00;">/
</span><span>f1.txt f2.txt
</span><span>
</span><span style="color:#7f8989;"># modified content
</span><span>$ cat f1.txt
</span><span>nice morning
</span><span>that was nice, just too good!
</span><span>$ cat f2.txt
</span><span>niceie goodbye
</span><span>
</span><span style="color:#7f8989;"># backed-up original content
</span><span>$ cat backups</span><span style="color:#72ab00;">/</span><span>f1.txt
</span><span>good morning
</span><span>that was good, just too good!
</span><span>$ cat backups</span><span style="color:#72ab00;">/</span><span>f2.txt
</span><span>goodie goodbye
</span></code></pre>
<p>Since <code>*</code> expands to the name of the input files, you can also use this feature when you need to add a prefix for the backups.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ sed </span><span style="color:#72ab00;">-</span><span>i</span><span style="color:#d07711;">'bkp.*' 's/green/yellow/'</span><span> colors.txt
</span><span>
</span><span>$ ls </span><span style="color:#72ab00;">*</span><span>colors</span><span style="color:#72ab00;">*
</span><span>bkp.colors.txt colors.txt
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> The <code>*</code> trick works with Perl as well, see <a href="https://learnbyexample.github.io/learn_perl_oneliners/in-place-file-editing.html">In-place file editing</a> chapter from my <a href="https://github.com/learnbyexample/learn_perl_oneliners">Perl One-Liners Guide</a> ebook for examples.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/ehxtbxR6qA0" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/learn_gnused">CLI text processing with GNU sed</a> ebook if you are interested in learning about the <code>GNU sed</code> command in more detail.</p>
Counting nested braces2021-12-15T00:00:00+00:002023-01-07T00:00:00+00:00https://learnbyexample.github.io/counting-nested-braces/<p>I posted a coding challenge in the fifth issue of <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a>. I discuss the problem and Python/Perl solutions in this blog post.</p>
<span id="continue-reading"></span><br>
<h2 id="problem-statement">Problem statement<a class="zola-anchor" href="#problem-statement" aria-label="Anchor link for: problem-statement">🔗</a></h2>
<p>Write a function that returns the maximum nested depth of curly braces for a given string input. For example:</p>
<ul>
<li><code>'a*{b+c}'</code> should return <code>1</code></li>
<li><code>'{{a+2}*{{b+{c*d}}+e*d}}'</code> should return <code>4</code></li>
<li>unbalanced or wrongly ordered braces like <code>'{{a}*b'</code> and <code>'}a+b{'</code> should return <code>-1</code></li>
</ul>
<br>
<h2 id="python-solution">Python solution<a class="zola-anchor" href="#python-solution" aria-label="Anchor link for: python-solution">🔗</a></h2>
<p>Here's one possible solution for this problem:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">def </span><span style="color:#c23f31;">max_nested_braces</span><span>(</span><span style="color:#5597d6;">expr</span><span>):
</span><span> max_count </span><span style="color:#72ab00;">= </span><span>count </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">0
</span><span> </span><span style="color:#72ab00;">for </span><span>char </span><span style="color:#72ab00;">in </span><span>expr:
</span><span> </span><span style="color:#72ab00;">if </span><span>char </span><span style="color:#72ab00;">== </span><span style="color:#d07711;">'{'</span><span>:
</span><span> count </span><span style="color:#72ab00;">+= </span><span style="color:#b3933a;">1
</span><span> </span><span style="color:#72ab00;">if </span><span>count </span><span style="color:#72ab00;">> </span><span>max_count:
</span><span> max_count </span><span style="color:#72ab00;">= </span><span>count
</span><span> </span><span style="color:#72ab00;">elif </span><span>char </span><span style="color:#72ab00;">== </span><span style="color:#d07711;">'}'</span><span>:
</span><span> </span><span style="color:#72ab00;">if </span><span>count </span><span style="color:#72ab00;">== </span><span style="color:#b3933a;">0</span><span>:
</span><span> </span><span style="color:#72ab00;">return -</span><span style="color:#b3933a;">1
</span><span> count </span><span style="color:#72ab00;">-= </span><span style="color:#b3933a;">1
</span><span>
</span><span> </span><span style="color:#72ab00;">if </span><span>count </span><span style="color:#72ab00;">!= </span><span style="color:#b3933a;">0</span><span>:
</span><span> </span><span style="color:#72ab00;">return -</span><span style="color:#b3933a;">1
</span><span> </span><span style="color:#72ab00;">return </span><span>max_count
</span></code></pre>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> In case you have trouble understanding the above code, you can use <a href="https://www.pythontutor.com/visualize.html#mode=edit">pythontutor</a> to visualize the code execution step-by-step.</p>
</blockquote>
<p>Here's an alternate solution using regular expressions:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">import </span><span>re
</span><span>
</span><span style="color:#72ab00;">def </span><span style="color:#c23f31;">max_nested_braces</span><span>(</span><span style="color:#5597d6;">expr</span><span>):
</span><span> count </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">0
</span><span> </span><span style="color:#72ab00;">while </span><span style="color:#b3933a;">True</span><span>:
</span><span> expr, no_of_subs </span><span style="color:#72ab00;">= </span><span>re.</span><span style="color:#5597d6;">subn</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\{[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">{}]</span><span style="color:#72ab00;">*</span><span style="color:#aeb52b;">\}</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">''</span><span>, expr)
</span><span> </span><span style="color:#72ab00;">if </span><span>no_of_subs </span><span style="color:#72ab00;">== </span><span style="color:#b3933a;">0</span><span>:
</span><span> </span><span style="color:#72ab00;">break
</span><span> count </span><span style="color:#72ab00;">+= </span><span style="color:#b3933a;">1
</span><span>
</span><span> </span><span style="color:#72ab00;">if </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">[{}]</span><span style="color:#d07711;">'</span><span>, expr):
</span><span> </span><span style="color:#72ab00;">return -</span><span style="color:#b3933a;">1
</span><span> </span><span style="color:#72ab00;">return </span><span>count
</span></code></pre>
<p>And if you are a fan of assignment expressions:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">import </span><span>re
</span><span>
</span><span style="color:#72ab00;">def </span><span style="color:#c23f31;">max_nested_braces</span><span>(</span><span style="color:#5597d6;">expr</span><span>):
</span><span> count </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">0
</span><span> </span><span style="color:#72ab00;">while </span><span>(op </span><span style="color:#72ab00;">:= </span><span>re.</span><span style="color:#5597d6;">subn</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\{[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">{}]</span><span style="color:#72ab00;">*</span><span style="color:#aeb52b;">\}</span><span style="color:#d07711;">'</span><span>, </span><span style="color:#d07711;">''</span><span>, expr)) </span><span style="color:#72ab00;">and </span><span>op[</span><span style="color:#b3933a;">1</span><span>]:
</span><span> expr </span><span style="color:#72ab00;">= </span><span>op[</span><span style="color:#b3933a;">0</span><span>]
</span><span> count </span><span style="color:#72ab00;">+= </span><span style="color:#b3933a;">1
</span><span>
</span><span> </span><span style="color:#72ab00;">if </span><span>re.</span><span style="color:#5597d6;">search</span><span>(</span><span style="color:#668f14;">r</span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">[{}]</span><span style="color:#d07711;">'</span><span>, expr):
</span><span> </span><span style="color:#72ab00;">return -</span><span style="color:#b3933a;">1
</span><span> </span><span style="color:#72ab00;">return </span><span>count
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> I verified these solutions using <code>assert</code> statements. See <a href="https://learnbyexample.github.io/100_page_python_intro/testing.html">Testing</a> chapter from my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook for more details.</p>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://learnbyexample.github.io/py_regular_expressions/working-with-matched-portions.html#resubn">Working with matched portions</a> chapter from my <a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> ebook for more details about the <code>re.subn()</code> function.</p>
<br>
<h2 id="perl-one-liner">Perl one-liner<a class="zola-anchor" href="#perl-one-liner" aria-label="Anchor link for: perl-one-liner">🔗</a></h2>
<p>Here's a solution for CLI enthusiasts:</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ cat ip.txt
</span><span>{a+2}*{b+c}
</span><span>{{a+2}*{{b+{c*d}}+e*d}}
</span><span>a*b{
</span><span>{{a+2}*{{b}+{c*d}}+e*d}}
</span><span>
</span><span>$ perl -lne '$c=0; $c++ while(s/\{[^{}]*\}//g);
</span><span> print /[{}]/ ? -1 : $c' ip.txt
</span><span>1
</span><span>4
</span><span>-1
</span><span>-1
</span></code></pre>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/learn_perl_oneliners">Perl One-Liners Guide</a> ebook if you are interested in learning to use Perl from the command-line.</p>
<p><img src="/images/info.svg" alt="info" /> If you are interested in <code>awk</code> and <code>bash</code> solutions, see <a href="https://unix.stackexchange.com/q/680920/109046">this unix.stackexchange thread</a>.</p>
Python tip 3: expression and result with f-string2021-12-14T00:00:00+00:002022-05-16T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-3/<p>In case you haven't yet discovered this awesome <strong>f-string</strong> feature, you can add <code>=</code> after an expression to get both the expression and the result in the output.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>num1 </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">42
</span><span style="color:#72ab00;">>>> </span><span>num2 </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">7
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{num1 </span><span style="color:#72ab00;">+ </span><span>num2 = }</span><span style="color:#d07711;">'
</span><span style="color:#d07711;">'num1 + num2 = 49'
</span><span style="color:#72ab00;">>>> </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{num1 </span><span style="color:#72ab00;">+ </span><span>(num2 </span><span style="color:#72ab00;">* </span><span style="color:#b3933a;">10</span><span>) = }</span><span style="color:#d07711;">'
</span><span style="color:#d07711;">'num1 + (num2 * 10) = 112'
</span></code></pre>
<p>I use it often to quickly test a function:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span style="background-color:#562d56bf;color:#f8f8f8;">def</span><span> </span><span style="color:#5597d6;">isodd</span><span>(n):
</span><span style="color:#b3933a;">... </span><span style="color:#72ab00;">return </span><span style="color:#a2a001;">bool</span><span>(n </span><span style="color:#72ab00;">% </span><span style="color:#b3933a;">2</span><span>)
</span><span style="color:#b3933a;">...
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">print</span><span>(</span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{</span><span style="color:#5597d6;">isodd</span><span>(</span><span style="color:#b3933a;">42</span><span>) = }</span><span style="color:#d07711;">'</span><span>)
</span><span style="color:#5597d6;">isodd</span><span>(</span><span style="color:#b3933a;">42</span><span>) </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">False
</span><span style="color:#72ab00;">>>> </span><span style="color:#b39f04;">print</span><span>(</span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{</span><span style="color:#5597d6;">isodd</span><span>(</span><span style="color:#b3933a;">123</span><span>) = }</span><span style="color:#d07711;">'</span><span>)
</span><span style="color:#5597d6;">isodd</span><span>(</span><span style="color:#b3933a;">123</span><span>) </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">True
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://docs.python.org/3/library/string.html#formatstrings">docs.python: Format String Syntax</a>, <a href="https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals">docs.python: Formatted string literals</a> and <a href="https://fstring.help/">fstring.help</a> for documentation and examples.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/QPJoqvq0toA" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook for a short, introductory guide for the Python programming language.</p>
Vim tip 1: increment/decrement numbers2021-12-08T00:00:00+00:002021-12-08T00:00:00+00:00https://learnbyexample.github.io/tips/vim-tip-1/<p>Did you know that you can easily increment or decrement a number in Vim?</p>
<ul>
<li><kbd>Ctrl</kbd>+<kbd>a</kbd> will increment the number under the cursor or the first occurrence of a number to the right of the cursor</li>
<li><kbd>Ctrl</kbd>+<kbd>x</kbd> will decrement the number under the cursor or the first occurrence of a number to the right of the cursor</li>
</ul>
<p>You can also provide a count prefix:</p>
<ul>
<li><kbd>3</kbd> followed by <kbd>Ctrl</kbd>+<kbd>a</kbd> will add <code>3</code></li>
<li><kbd>1000</kbd> followed by <kbd>Ctrl</kbd>+<kbd>x</kbd> will subtract <code>1000</code></li>
</ul>
<p>Numbers prefixed with <code>0</code>, <code>0x</code> and <code>0b</code> will be treated as octal, hexadecimal and binary respectively. You can also use uppercase for <code>x</code> and <code>b</code>. What if you want numbers prefixed with <code>0</code> to be treated as decimal? You can use the <code>nrformats</code> setting as shown below:</p>
<pre data-lang="vim" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-vim "><code class="language-vim" data-lang="vim"><span style="color:#b39f04;">set</span><span> nrformats-=octal
</span></code></pre>
<p>Decimal numbers prefixed with <code>-</code> will be treated as negative numbers. For example, using <kbd>Ctrl</kbd>+<kbd>a</kbd> on <code>-100</code> will give you <code>-99</code>. While this is handy, this trips me up often when dealing with date formats like 2021-12-08.</p>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://vimhelp.org/change.txt.html#CTRL-A">:h ctrl-a</a>, <a href="https://vimhelp.org/change.txt.html#CTRL-X">:h ctrl-x</a> and <a href="https://vimhelp.org/options.txt.html#%27nrformats%27">:h nrformats</a> for documentation.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/slB98yJ7lv0" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://learnbyexample.github.io/curated_resources/vim.html">curated list of resources for Vim</a> and <a href="https://github.com/learnbyexample/vim_reference">Vim Reference Guide</a>.</p>
Improve your Python regex skills with 75 interactive exercises2021-12-01T00:00:00+00:002023-03-20T00:00:00+00:00https://learnbyexample.github.io/python-25-days-of-regex/<p><strong>(2023-03-20) Update:</strong> This <a href="https://github.com/learnbyexample/TUI-apps/blob/main/PyRegexExercises">TUI app</a> covers many more exercises compared to the GUI app discussed below.</p>
<hr>
<p>Still confused about Python regular expressions? Grow your confidence with <a href="https://github.com/learnbyexample/py_regular_expressions">Understanding Python re(gex)?</a> ebook (FREE this month!) and an <a href="https://github.com/learnbyexample/py_regular_expressions/tree/8433b34bd3f03662abac25c754a5ecf871712980/interactive_exercises">interactive GUI app</a>.</p>
<p>Inspired by <a href="https://adventofcode.com/">Advent of Code</a>, I'll also be posting <a href="https://twitter.com/learn_byexample/status/1465998844898918403">3 challenges per day on twitter</a> for 25 days.</p>
<span id="continue-reading"></span><br>
<h2 id="free-ebook">Free ebook<a class="zola-anchor" href="#free-ebook" aria-label="Anchor link for: free-ebook">🔗</a></h2>
<p>My post about the interactive GUI app made it to the <a href="https://news.ycombinator.com/item?id=29391107">Hacker News front page</a>. To celebrate, you can get PDF/EPUB versions of my <strong>Understanding Python re(gex)?</strong> ebook for free using either of the below links. The offer is valid till 31-Dec-2021.</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/py_regex">Gumroad</a></li>
<li><a href="https://leanpub.com/py_regex">Leanpub</a></li>
</ul>
<p>Or, you can use the <a href="https://learnbyexample.github.io/py_regular_expressions/">web version</a> if you prefer reading the book online.</p>
<br>
<h2 id="interactive-gui-app">Interactive GUI app<a class="zola-anchor" href="#interactive-gui-app" aria-label="Anchor link for: interactive-gui-app">🔗</a></h2>
<p>Based on the <strong>Understanding Python re(gex)?</strong> book contents as well as the exercises, I made an <a href="https://github.com/learnbyexample/py_regular_expressions/tree/8433b34bd3f03662abac25c754a5ecf871712980/interactive_exercises">interactive GUI app</a> with 75 questions on <code>re.search</code>, <code>re.sub</code>, <code>re.split</code> and <code>re.findall</code> that'll test your understanding of anchors, alternation, grouping, escaping metacharacters, dot metacharacter, quantifiers, character class, grouping, lookarounds, flags, etc.</p>
<p>Here's some screenshots:</p>
<p align="center"><img src="/images/python_exercises/search.png" alt="Python exercise example for re.search" /></p>
<p align="center"><img src="/images/python_exercises/sub.png" alt="Python exercise example for re.sub" /></p>
<p align="center"><img src="/images/python_exercises/split.png" alt="Python exercise example for re.split" /></p>
<p align="center"><img src="/images/python_exercises/findall.png" alt="Python exercise example for re.findall" /></p>
<p>And here's a brief demo:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/Ytz-E-rRdX4" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<h2 id="25-days-of-regex">25 Days Of Regex<a class="zola-anchor" href="#25-days-of-regex" aria-label="Anchor link for: 25-days-of-regex">🔗</a></h2>
<p>If 75 exercises seem daunting to you, consider doing 3 exercises per day. Allocate some time everyday to read the book and complete 3 challenges.</p>
<p>I'd also be posting <a href="https://twitter.com/learn_byexample/status/1465998844898918403">3 challenges per day on twitter</a>, where you'll be able to get help from me and fellow programmers.</p>
<br>
<p>Happy learning :)</p>
CLI tip 2: counting number of matches2021-11-30T00:00:00+00:002022-05-27T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-2/<p>Use <code>grep -c</code> to count the number of input <em>lines</em> containing a given pattern.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># number of input lines containing 'a'
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'goal\nrate\neat\npit' </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>c </span><span style="color:#d07711;">'a'
</span><span style="color:#b3933a;">3
</span><span>
</span><span style="color:#7f8989;"># number of input lines containing all the vowels
</span><span>$ grep </span><span style="color:#72ab00;">-</span><span>icP </span><span style="color:#d07711;">'^(?=.*a)(?=.*e)(?=.*i)(?=.*o).*u' </span><span style="color:#72ab00;">/</span><span>usr</span><span style="color:#72ab00;">/</span><span>share</span><span style="color:#72ab00;">/</span><span>dict</span><span style="color:#72ab00;">/</span><span>words
</span><span style="color:#b3933a;">640
</span><span>
</span><span style="color:#7f8989;"># number of input lines NOT containing 'at'
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'goal\nrate\neat\npit' </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>vc </span><span style="color:#d07711;">'at'
</span><span style="color:#b3933a;">2
</span></code></pre>
<p>With multiple file input, count is displayed for each file <em>separately</em>. Use <code>cat</code> if you need a combined count.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># separate count for each input file
</span><span>$ grep </span><span style="color:#72ab00;">-</span><span>c </span><span style="color:#d07711;">'a'</span><span> names.txt purchases.txt
</span><span>names.txt</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">2
</span><span>purchases.txt</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">6
</span><span>
</span><span style="color:#7f8989;"># total count for all the input files
</span><span>$ cat names.txt purchases.txt </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>c </span><span style="color:#d07711;">'a'
</span><span style="color:#b3933a;">8
</span></code></pre>
<p>If total number of matches is required, use the <code>-o</code> option to display only the matching portions (one per line) and then use <code>wc</code> to get the count.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># -c gives count of matching lines only
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'goal\nrate\neat\npit' </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>c </span><span style="color:#d07711;">'[aeiou]'
</span><span style="color:#b3933a;">4
</span><span>
</span><span style="color:#7f8989;"># use -o to get each match on a separate line
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'goal\nrate\neat\npit' </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#72ab00;">-</span><span>o </span><span style="color:#d07711;">'[aeiou]' </span><span style="color:#72ab00;">|</span><span> wc </span><span style="color:#72ab00;">-</span><span>l
</span><span style="color:#b3933a;">7
</span></code></pre>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> Note that if you use <a href="https://github.com/BurntSushi/ripgrep">ripgrep</a>, you can simply use <code>-co</code> or <code>--count-matches</code> instead of piping to the <code>wc</code> command.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># this behavior is different compared to GNU grep
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'goal\nrate\neat\npit' </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">-</span><span>co </span><span style="color:#d07711;">'[aeiou]'
</span><span style="color:#b3933a;">7
</span></code></pre>
</blockquote>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/pZ4btzZKYVg" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep">CLI text processing with GNU grep and ripgrep</a> ebook if you are interested in learning about <code>GNU grep</code> and <code>ripgrep</code> commands in more detail.</p>
Programming deals2021-11-26T00:00:00+00:002021-11-27T00:00:00+00:00https://learnbyexample.github.io/programming-deals/<p>Hello!</p>
<p>Here's some exciting programming deals for my own ebooks as well sales details from other creators.</p>
<span id="continue-reading"></span><br>
<p><strong>Offers for my ebooks</strong> (valid till Nov 30)</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/py_projects/blackfriday">Practice Python Projects</a> — FREE (normal price $10)</li>
<li><a href="https://learnbyexample.gumroad.com/l/python-bundle/blackfriday">Learn by example Python bundle</a> — $2 (normal price $12)</li>
<li><a href="https://learnbyexample.gumroad.com/l/all-books/blackfriday">All 11 Books Bundle</a> — $5 (normal price $22)</li>
<li><a href="https://twitter.com/learn_byexample/status/1463898778860068868">Giveaway contest on twitter</a> — a chance to get a single ebook for free</li>
</ul>
<br>
<p><strong>Indie creators</strong></p>
<ul>
<li><a href="https://mathspp.gumroad.com/l/pythonbootcamp/blackfriday">Python Problem-Solving Bootcamp</a> — 40% OFF today, 30% OFF tomorrow and so on (boost your Python problem-solving skills)</li>
<li><a href="https://driscollis.gumroad.com/">Python books by Michael Driscoll</a> — $10 OFF on any book using coupon code "black21" (Python 101/201, Image/PDF/Excel processing, etc)
<ul>
<li>see also author's <a href="https://www.blog.pythonlibrary.org/2021/11/24/python-black-friday-cyber-monday-sales-2021/">blog post</a> for links to other Python sales</li>
</ul>
</li>
<li><a href="https://www.pythonmorsels.com/">Python Morsels</a> — 50% OFF until Nov 30 (skill-building service with short videos and hands-on bite-sized Python exercises)</li>
</ul>
<br>
<p><strong>Miscellaneous</strong></p>
<ul>
<li><a href="https://mailchi.mp/leanpub/monthly-sale-2021-black-friday">The Leanpub Monthly Sale for November 2021</a></li>
<li><a href="https://www.humblebundle.com/books/code-like-pro-manning-publications-books">Humble Book Bundle: Code Like a Pro by Manning Publications</a> — various sale options starting from $1</li>
<li><a href="https://news.ycombinator.com/item?id=29338976">Various programming deals discussion on Hacker News</a></li>
<li><a href="https://devutils.app/pricing/">DevUtils.app</a> — 30% OFF this week (Powerful developer tools for your everyday tasks, Native macOS app)</li>
<li><a href="https://bhavaniravi.gumroad.com/l/AxFMK">How to Journal to Live your Best Life?</a> — $1.99 for 30 customers, $3.99 for the next 30 customers and so on (not strictly related to programming, applicable for life events, career, etc)</li>
</ul>
<br>
<p>Happy learning :)</p>
Numeric Palindrome2021-11-25T00:00:00+00:002021-11-25T00:00:00+00:00https://learnbyexample.github.io/numeric-palindrome/<p>I posted a coding challenge in the second issue of <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a>. I discuss the problem and Python/Perl solutions in this blog post.</p>
<span id="continue-reading"></span><br>
<h2 id="problem-statement">Problem statement<a class="zola-anchor" href="#problem-statement" aria-label="Anchor link for: problem-statement">🔗</a></h2>
<p>Find numbers from <code>1</code> to <code>10000</code> (inclusive) which reads the same in reversed form in both binary and decimal formats. For example, <code>33</code> in decimal is <code>100001</code> in binary and both of these are palindromic.</p>
<br>
<h2 id="python-solution">Python solution<a class="zola-anchor" href="#python-solution" aria-label="Anchor link for: python-solution">🔗</a></h2>
<p>Here's one possible solution for this problem:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">for </span><span>n </span><span style="color:#72ab00;">in </span><span style="color:#b39f04;">range</span><span>(</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">10001</span><span>):
</span><span> dec_s </span><span style="color:#72ab00;">= </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{n}</span><span style="color:#d07711;">'
</span><span> bin_s </span><span style="color:#72ab00;">= </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{n</span><span style="color:#b3933a;">:b</span><span>}</span><span style="color:#d07711;">'
</span><span> </span><span style="color:#72ab00;">if </span><span>dec_s </span><span style="color:#72ab00;">== </span><span>dec_s[::</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">1</span><span>] </span><span style="color:#72ab00;">and </span><span>bin_s </span><span style="color:#72ab00;">== </span><span>bin_s[::</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">1</span><span>]:
</span><span> </span><span style="color:#b39f04;">print</span><span>(n)
</span></code></pre>
<p>Extending the above solution to include more comparisons is easy with built-in features:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">for </span><span>n </span><span style="color:#72ab00;">in </span><span style="color:#b39f04;">range</span><span>(</span><span style="color:#b3933a;">1</span><span>, </span><span style="color:#b3933a;">10001</span><span>):
</span><span> dec_s </span><span style="color:#72ab00;">= </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{n}</span><span style="color:#d07711;">'
</span><span> bin_s </span><span style="color:#72ab00;">= </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{n</span><span style="color:#b3933a;">:b</span><span>}</span><span style="color:#d07711;">'
</span><span> oct_s </span><span style="color:#72ab00;">= </span><span style="color:#668f14;">f</span><span style="color:#d07711;">'</span><span>{n</span><span style="color:#b3933a;">:o</span><span>}</span><span style="color:#d07711;">'
</span><span> </span><span style="color:#72ab00;">if </span><span style="color:#b39f04;">all</span><span>(s </span><span style="color:#72ab00;">== </span><span>s[::</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">1</span><span>] </span><span style="color:#72ab00;">for </span><span>s </span><span style="color:#72ab00;">in </span><span>(dec_s, bin_s, oct_s)):
</span><span> </span><span style="color:#b39f04;">print</span><span>(n)
</span></code></pre>
<p>As an exercise, extend this program further to include hexadecimal number comparison as well. Can you find out what's the first number that is greater than ten to satisfy all the four numeric formats?</p>
<br>
<h2 id="perl-one-liner">Perl one-liner<a class="zola-anchor" href="#perl-one-liner" aria-label="Anchor link for: perl-one-liner">🔗</a></h2>
<p>Here's a solution for CLI enthusiasts:</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> perl</span><span style="color:#5597d6;"> -le </span><span style="color:#d07711;">'for (1..10000) { $bn = sprintf("%b", $_);
</span><span style="color:#d07711;"> print if ($_ eq reverse) && ($bn eq reverse $bn) }'
</span><span style="color:#5597d6;">1
</span><span style="color:#5597d6;">3
</span><span style="color:#5597d6;">5
</span><span style="color:#5597d6;">7
</span><span style="color:#5597d6;">9
</span><span style="color:#5597d6;">33
</span><span style="color:#5597d6;">99
</span><span style="color:#5597d6;">313
</span><span style="color:#5597d6;">585
</span><span style="color:#5597d6;">717
</span><span style="color:#5597d6;">7447
</span><span style="color:#5597d6;">9009
</span></code></pre>
<br>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://github.com/learnbyexample/learn_perl_oneliners">Perl One-Liners Guide</a> ebook if you are interested in learning to use Perl from the command-line.</p>
Python tip 2: membership operator2021-11-25T00:00:00+00:002022-05-16T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-2/<p>The <code>in</code> membership operator checks if a given value is part of a collection of values. Here's an example with <code>range()</code> function:</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>num </span><span style="color:#72ab00;">= </span><span style="color:#b3933a;">5
</span><span>
</span><span style="color:#7f8989;"># checks if num is present among the integers 3 or 4 or 5
</span><span style="color:#72ab00;">>>> </span><span>num </span><span style="color:#72ab00;">in </span><span style="color:#b39f04;">range</span><span>(</span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">6</span><span>)
</span><span style="color:#b3933a;">True
</span></code></pre>
<p>Instead of a series of <code>==</code> comparisons combined with the <code>or</code> boolean operator, you can utilize the <code>in</code> operator.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>pet </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'cat'
</span><span>
</span><span style="color:#7f8989;"># instead of doing this
</span><span style="color:#72ab00;">>>> </span><span>pet </span><span style="color:#72ab00;">== </span><span style="color:#d07711;">'bat' </span><span style="color:#72ab00;">or </span><span>pet </span><span style="color:#72ab00;">== </span><span style="color:#d07711;">'cat' </span><span style="color:#72ab00;">or </span><span>pet </span><span style="color:#72ab00;">== </span><span style="color:#d07711;">'dog'
</span><span style="color:#b3933a;">True
</span><span>
</span><span style="color:#7f8989;"># use the membership operator
</span><span style="color:#72ab00;">>>> </span><span>pet </span><span style="color:#72ab00;">in </span><span>(</span><span style="color:#d07711;">'bat'</span><span>, </span><span style="color:#d07711;">'cat'</span><span>, </span><span style="color:#d07711;">'dog'</span><span>)
</span><span style="color:#b3933a;">True
</span></code></pre>
<p>When applied to strings, the <code>in</code> operator performs substring comparison.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>fruit </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'mango'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">'an' </span><span style="color:#72ab00;">in </span><span>fruit
</span><span style="color:#b3933a;">True
</span><span style="color:#72ab00;">>>> </span><span style="color:#d07711;">'at' </span><span style="color:#72ab00;">in </span><span>fruit
</span><span style="color:#b3933a;">False
</span></code></pre>
<p>To invert the membership test, use the <code>not in</code> operator.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>pet </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'parrot'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>pet </span><span style="color:#72ab00;">in </span><span>(</span><span style="color:#d07711;">'bat'</span><span>, </span><span style="color:#d07711;">'cat'</span><span>, </span><span style="color:#d07711;">'dog'</span><span>)
</span><span style="color:#b3933a;">False
</span><span style="color:#72ab00;">>>> </span><span>pet </span><span style="color:#72ab00;">not in </span><span>(</span><span style="color:#d07711;">'bat'</span><span>, </span><span style="color:#d07711;">'cat'</span><span>, </span><span style="color:#d07711;">'dog'</span><span>)
</span><span style="color:#b3933a;">True
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/TmVJPtZBRv4" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://docs.python.org/3/reference/expressions.html#membership-test-operations">docs.python: Membership test operations</a> for documentation. See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
CLI tip 1: remove metadata from images2021-11-18T00:00:00+00:002022-05-03T00:00:00+00:00https://learnbyexample.github.io/tips/cli-tip-1/<p>Want to remove metadata (DateTime, Model, Orientation, ShutterSpeedValue, etc) from your images? You can use <code>mogrify</code> or <code>convert</code> tools provided by ImageMagick.</p>
<p>GUI image viewer applications will usually allow you to see some of the image metadata. You can also use the <code>identify</code> command line tool to get all the metadata:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># remove 'head' to get the entire list of metadata
</span><span>$ identify </span><span style="color:#72ab00;">-</span><span>verbose insect.jpg </span><span style="color:#72ab00;">|</span><span> grep </span><span style="color:#d07711;">'exif' </span><span style="color:#72ab00;">|</span><span> head
</span><span> </span><span style="color:#b3933a;">exif:ApertureValue: 113</span><span style="color:#72ab00;">/</span><span style="color:#b3933a;">32
</span><span> </span><span style="color:#b3933a;">exif:ColorSpace: 1
</span><span> </span><span style="color:#b3933a;">exif:ComponentsConfiguration: 1</span><span>, </span><span style="color:#b3933a;">2</span><span>, </span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">0
</span><span> </span><span style="color:#b3933a;">exif:CompressedBitsPerPixel: 3</span><span style="color:#72ab00;">/</span><span style="color:#b3933a;">1
</span><span> </span><span style="color:#b3933a;">exif:CustomRendered: 0
</span><span> </span><span style="color:#b3933a;">exif:DateTime: 2016</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">12</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">03 11</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">26</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">10
</span><span> </span><span style="color:#b3933a;">exif:DateTimeDigitized: 2016</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">12</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">03 11</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">26</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">10
</span><span> </span><span style="color:#b3933a;">exif:DateTimeOriginal: 2016</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">12</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">03 11</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">26</span><span style="color:#72ab00;">:</span><span style="color:#b3933a;">10
</span><span> </span><span style="color:#b3933a;">exif:DigitalZoomRatio: 4000</span><span style="color:#72ab00;">/</span><span style="color:#b3933a;">4000
</span><span> </span><span style="color:#b3933a;">exif:ExifOffset: 240
</span></code></pre>
<p>And here's how you can remove such metadata from images:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># to create a new image with metadata removed
</span><span>$ convert </span><span style="color:#72ab00;">-</span><span>strip insect.jpg op.jpg
</span><span>
</span><span style="color:#7f8989;"># to modify the input image itself
</span><span>$ mogrify </span><span style="color:#72ab00;">-</span><span>strip insect.jpg
</span></code></pre>
<p>You can also pass multiple images to <code>mogrify</code>:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ mogrify </span><span style="color:#72ab00;">-</span><span>strip </span><span style="color:#72ab00;">*</span><span>.jpg
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> Note that the image size after metadata removal may vary because of recompression.</p>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/p0KCLusMd5Q" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><strong>Further Reading</strong>:</p>
<ul>
<li><a href="https://imagemagick.org/">ImageMagick</a> — create, edit, compose, or convert digital images</li>
<li><a href="https://askubuntu.com/questions/260810/how-can-i-read-and-remove-meta-exif-data-from-my-photos-using-the-command-line">How can I read and remove meta (exif) data from my photos using the command line?</a></li>
<li><a href="https://unix.stackexchange.com/questions/312754/how-to-strip-metadata-from-image-files">How to strip metadata from image files</a></li>
<li><a href="https://softwareengineering.stackexchange.com/questions/42767/why-do-they-name-a-program-mogrify-in-imagemagick">What does mogrify mean?</a></li>
<li><a href="https://en.wikipedia.org/wiki/Exif">wikipedia: Exif</a></li>
</ul>
Python tip 1: tuple argument for startswith/endswith methods2021-11-16T00:00:00+00:002022-04-29T00:00:00+00:00https://learnbyexample.github.io/tips/python-tip-1/<p>You'd probably know about the <code>startswith()</code> and <code>endswith()</code> string methods.</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>sentence </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'This is a sample string'
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>sentence.</span><span style="color:#5597d6;">startswith</span><span>(</span><span style="color:#d07711;">'This'</span><span>)
</span><span style="color:#b3933a;">True
</span><span style="color:#72ab00;">>>> </span><span>sentence.</span><span style="color:#5597d6;">startswith</span><span>(</span><span style="color:#d07711;">'is'</span><span>)
</span><span style="color:#b3933a;">False
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>sentence.</span><span style="color:#5597d6;">endswith</span><span>(</span><span style="color:#d07711;">'ing'</span><span>)
</span><span style="color:#b3933a;">True
</span><span style="color:#72ab00;">>>> </span><span>sentence.</span><span style="color:#5597d6;">endswith</span><span>(</span><span style="color:#d07711;">'ly'</span><span>)
</span><span style="color:#b3933a;">False
</span></code></pre>
<p>But did you know that you can also pass a <code>tuple</code> of strings?</p>
<pre data-lang="python" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#72ab00;">>>> </span><span>words </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'refuse'</span><span>, </span><span style="color:#d07711;">'impossible'</span><span>, </span><span style="color:#d07711;">'fire'</span><span>, </span><span style="color:#d07711;">'present'</span><span>, </span><span style="color:#d07711;">'read'</span><span>, </span><span style="color:#d07711;">'shim'</span><span>]
</span><span style="color:#72ab00;">>>> </span><span>prefix </span><span style="color:#72ab00;">= </span><span>(</span><span style="color:#d07711;">'im'</span><span>, </span><span style="color:#d07711;">'re'</span><span>, </span><span style="color:#d07711;">'use'</span><span>)
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>[w </span><span style="color:#72ab00;">for </span><span>w </span><span style="color:#72ab00;">in </span><span>words </span><span style="color:#72ab00;">if </span><span>w.</span><span style="color:#5597d6;">startswith</span><span>(prefix)]
</span><span>[</span><span style="color:#d07711;">'refuse'</span><span>, </span><span style="color:#d07711;">'impossible'</span><span>, </span><span style="color:#d07711;">'read'</span><span>]
</span><span>
</span><span style="color:#72ab00;">>>> </span><span>[w </span><span style="color:#72ab00;">for </span><span>w </span><span style="color:#72ab00;">in </span><span>words </span><span style="color:#72ab00;">if </span><span>w.</span><span style="color:#5597d6;">endswith</span><span>(prefix)]
</span><span>[</span><span style="color:#d07711;">'refuse'</span><span>, </span><span style="color:#d07711;">'fire'</span><span>, </span><span style="color:#d07711;">'shim'</span><span>]
</span></code></pre>
<p><strong>Video demo</strong>:</p>
<p align="center"><iframe width="560" height="315" loading="lazy" src="https://www.youtube.com/embed/THSMmCZQn1A" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<br>
<p><img src="/images/info.svg" alt="info" /> See also my <a href="https://github.com/learnbyexample/100_page_python_intro">100 Page Python Intro</a> ebook.</p>
Announcing learnbyexample weekly newsletter2021-11-13T00:00:00+00:002022-12-22T00:00:00+00:00https://learnbyexample.github.io/learnbyexample-weekly-newsletter/<p>Hello!</p>
<p>I'm excited to announce <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a> newsletter, scheduled to be delivered every Friday.</p>
<p>This free newsletter will help you discover awesome programming resources. I'll primarily focus on resources related to Python, Linux, CLI tools, Regular Expressions and Vim. Sometimes, I'll also include other programming resources.</p>
<span id="continue-reading"></span><br>
<p align="center"><a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"><img src="/images/learnbyexample-weekly.png" alt="learnbyexample weekly newsletter" /></a></p>
<br>
<p>You can expect 5-15 links, usually categorized into the following sections:</p>
<ul>
<li>Article of the week</li>
<li>Resources</li>
<li>Free programming books, courses and deals</li>
<li>Tip of the week</li>
<li>Tools</li>
<li>Curiosity Corner</li>
</ul>
<p>Here are some of the resource links from the first issue:</p>
<ul>
<li><a href="https://www.joshwcomeau.com/blog/how-to-learn-stuff-quickly/">How To Learn Stuff Quickly</a> by Josh W. Comeau</li>
<li><a href="https://miguendes.me/python-pathlib">Python pathlib Cookbook</a> — 57+ Examples to Master It</li>
<li><a href="https://shrutibalasa.gumroad.com/l/css-flex-and-grid/Newsoff25">Complete Guide to CSS Flex and Grid</a> by Shruti Balasa (25% OFF for a week)</li>
<li><a href="https://carbon.now.sh/">Carbon</a> — Create and share beautiful images of your source code</li>
</ul>
<p>After subscribing to <a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly">learnbyexample weekly</a>, you'll get a confirmation email followed by another email with the latest issue contents. You can also view the past issues from your Gumroad account.</p>
<p>Hope you find the newsletter useful. Let me know your feedback via email ([email protected]) or <a href="https://twitter.com/learn_byexample">twitter</a>.</p>
<p>Happy learning :)</p>
The Indie Python Extravaganza2021-10-01T00:00:00+00:002022-12-22T00:00:00+00:00https://learnbyexample.github.io/indie-python-extravaganza/<p>Hello!</p>
<p>You never know where a conversation between indie authors will lead to. A <a href="https://twitter.com/tw_lgiordani/status/1442489206681395207">tweet about Leanpub Python book sales</a> brought up giveaways that we indie authors tend to do. Long story short, the four of us ended up deciding to create <strong>The Indie Python Extravaganza</strong> bundle.</p>
<p>And guess what?! You can use this <a href="https://leanpub.com/b/theindiepythonextravaganza/c/pytober">pytober coupon link</a> to get the bundle for FREE (the offer is valid till 31-Oct-2021).</p>
<span id="continue-reading"></span><br>
<h2 id="bundle-contents">Bundle contents<a class="zola-anchor" href="#bundle-contents" aria-label="Anchor link for: bundle-contents">🔗</a></h2>
<blockquote>
<p>A collection of books that will help you to improve your knowledge of the Python programming language one page at a time. Join four indie authors in a journey from the basics of Python to the structure of production-ready systems, going through the core features of the language, some intermediate projects and a deep dive into regular expressions.</p>
</blockquote>
<p align="center"><img src="/images/books/indie_python.jpg" alt="The Indie Python Extravaganza cover image" /></p>
<p align="center"><a href="https://leanpub.com/b/theindiepythonextravaganza/c/pytober">Coupon link</a></p>
<blockquote>
<p>In this bundle, Mike will teach you the basics of Python with <strong>Python 101</strong>. Sundeep will then take the lead and help you to put your knowledge into practice with <strong>Practice Python Projects</strong>. Learn what NOT to do when writing your Python programs with Rodrigo in his <strong>Pydon'ts</strong> book! If you need to learn regular expressions, Sundeep has again your back with his <strong>Python re(gex)?</strong> book, and when you are ready to start working on production code, you'll have <strong>Clean Architectures in Python</strong> to help you!</p>
</blockquote>
<br>
<h2 id="authors">Authors<a class="zola-anchor" href="#authors" aria-label="Anchor link for: authors">🔗</a></h2>
<ul>
<li>Leonardo Giordani: <a href="https://www.thedigitalcatonline.com">Blog</a>, <a href="https://twitter.com/tw_lgiordani">Twitter</a></li>
<li>Michael Driscoll: <a href="https://www.blog.pythonlibrary.org">Blog</a>, <a href="https://twitter.com/driscollis">Twitter</a></li>
<li>Rodrigo Girão Serrão: <a href="https://mathspp.com">Blog</a>, <a href="https://twitter.com/mathsppblog">Twitter</a></li>
<li>Sundeep Agarwal: <a href="https://learnbyexample.github.io/">Blog</a>, <a href="https://twitter.com/learn_byexample">Twitter</a></li>
</ul>
<br>
<h2 id="how-can-you-help">How can you help?<a class="zola-anchor" href="#how-can-you-help" aria-label="Anchor link for: how-can-you-help">🔗</a></h2>
<p>Share the bundle link with your friends and colleagues interested in learning Python.</p>
<p>Your feedback on the book contents would be appreciated even more.</p>
<p>Happy learning 😇</p>
Practice Python Projects book announcement2021-07-30T00:00:00+00:002025-07-10T00:00:00+00:00https://learnbyexample.github.io/practice-python-projects-book-announcement/<p>Hello!</p>
<p>My "<strong>Practice Python Projects</strong>" ebook presents five beginner-to-intermediate level projects inspired by real world use cases:</p>
<ul>
<li><a href="https://learnbyexample.github.io/practice_python_projects/calculator/calculator.html">Enhance your CLI experience with a custom Python calculator</a></li>
<li><a href="https://learnbyexample.github.io/practice_python_projects/poll_data_analysis/poll_data_analysis.html">Analyzing poll data from a Reddit comment thread</a></li>
<li><a href="https://learnbyexample.github.io/practice_python_projects/find_typos/find_typos.html">Finding typos in plain text and Markdown files</a></li>
<li><a href="https://learnbyexample.github.io/practice_python_projects/mcq/multiple_choice_questions.html">Creating a GUI for evaluating multiple choice questions</a></li>
<li><a href="https://learnbyexample.github.io/practice_python_projects/square_tic_tac_toe/square_tic_tac_toe.html">Square Tic Tac Toe — creating a GUI game with AI logic</a></li>
</ul>
<p>To test your understanding and to make it more interesting, you'll also be presented with exercises at the end of each project. Resources for further exploration are also mentioned throughout the book.</p>
<span id="continue-reading"></span><br>
<h2 id="ebook-links">Ebook links<a class="zola-anchor" href="#ebook-links" aria-label="Anchor link for: ebook-links">🔗</a></h2>
<p>You can buy the <strong>PDF/EPUB</strong> versions of the ebook using these links:</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/py_projects">https://learnbyexample.gumroad.com/l/py_projects</a></li>
<li><a href="https://leanpub.com/py_projects">https://leanpub.com/py_projects</a></li>
</ul>
<p>You can also get them as part of the <strong>Learn by example Python bundle</strong> using these links:</p>
<ul>
<li><a href="https://learnbyexample.gumroad.com/l/python-bundle">https://learnbyexample.gumroad.com/l/python-bundle</a></li>
<li><a href="https://leanpub.com/b/python-bundle">https://leanpub.com/b/python-bundle</a></li>
</ul>
<br>
<h2 id="videos">Videos<a class="zola-anchor" href="#videos" aria-label="Anchor link for: videos">🔗</a></h2>
<p align="center"><iframe width="560" height="315" src="https://www.youtube.com/embed/5whwiiURWS8" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></p>
<p>Check out my <a href="https://learnbyexample.github.io/tips/">programming tips</a> covering Python, command line tools and Vim:</p>
<ul>
<li><a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F">Python tips</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY">Linux command line tips</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4NN2tK-59ZiNBm-o64-Yvos">Vim tips</a></li>
</ul>
<br>
<h2 id="testimonials">Testimonials<a class="zola-anchor" href="#testimonials" aria-label="Anchor link for: testimonials">🔗</a></h2>
<blockquote>
<p>Your Practice Python Projects book is really helping me to reinforce my knowledge and mastery of Python as I'm learning.</p>
<p>— <a href="https://twitter.com/tayporware/status/1446499855988400129">feedback on twitter</a></p>
</blockquote>
<br>
<h2 id="web-version">Web version<a class="zola-anchor" href="#web-version" aria-label="Anchor link for: web-version">🔗</a></h2>
<p>You can also read the book online here: <a href="https://learnbyexample.github.io/practice_python_projects/preface.html">https://learnbyexample.github.io/practice_python_projects/preface.html</a>.</p>
<br>
<h2 id="github-repo">GitHub repo<a class="zola-anchor" href="#github-repo" aria-label="Anchor link for: github-repo">🔗</a></h2>
<p>Visit <a href="https://github.com/learnbyexample/practice_python_projects">https://github.com/learnbyexample/practice_python_projects</a> for programs, example files, markdown source and other details about the book.</p>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://learnbyexample.github.io/customizing-pandoc/">my blog post</a> on how to customize <code>pandoc</code> for generating beautiful PDF/EPUB versions from GitHub style markdown.</p>
</blockquote>
<br>
<h2 id="feedback">Feedback<a class="zola-anchor" href="#feedback" aria-label="Anchor link for: feedback">🔗</a></h2>
<p>I would highly appreciate it if you'd <strong>let me know how you felt about this book</strong>. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.</p>
<p>You can reach me via:</p>
<ul>
<li>Issue Manager: <a href="https://github.com/learnbyexample/practice_python_projects/issues">https://github.com/learnbyexample/practice_python_projects/issues</a></li>
<li>E-mail: <code>echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode</code></li>
<li>Twitter: <a href="https://twitter.com/learn_byexample">https://twitter.com/learn_byexample</a></li>
</ul>
<p>Happy learning :)</p>
Escaping madness to get literal field separators in awk2021-07-02T00:00:00+00:002023-01-07T00:00:00+00:00https://learnbyexample.github.io/escaping-madness-awk-literal-field-separator/<p>I'm building a tool called <a href="https://github.com/learnbyexample/regexp-cut">rcut</a> that allows you to use <code>cut</code> like syntax with features like regexp based delimiters. The solution uses <code>awk</code> inside a <code>bash</code> script.</p>
<p>Latest <a href="https://en.wikipedia.org/wiki/Feature_creep">feature creep</a> is fixed string field splitting. I thought it would be a simple enough solution to add.</p>
<p>I was wrong.</p>
<span id="continue-reading"></span><br>
<h2 id="how-many-escapes-for-a-single-backslash">How many escapes for a single backslash?<a class="zola-anchor" href="#how-many-escapes-for-a-single-backslash" aria-label="Anchor link for: how-many-escapes-for-a-single-backslash">🔗</a></h2>
<p>For reference, these are the versions I have on my machine:</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> gawk</span><span style="color:#5597d6;"> --version
</span><span style="color:#5597d6;">GNU</span><span> Awk 5.1.0, API: 3.0
</span><span>
</span><span style="color:#5597d6;">$</span><span> mawk</span><span style="color:#5597d6;"> -W</span><span> version
</span><span style="color:#5597d6;">mawk</span><span> 1.3.4 20200120
</span></code></pre>
<p><code>mawk</code> and <code>gawk</code> differ when it comes to escaping backslashes. You'll later see the rule that'll work correctly for both implementations.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'apple\bake\cake' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">mawk -F</span><span style="color:#d07711;">'e\' '{print $2}'
</span><span style="color:#5597d6;">bak
</span><span>
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'apple\bake\cake' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk -F</span><span style="color:#d07711;">'e\' '{print $2}'
</span><span style="color:#5597d6;">gawk:</span><span> fatal: invalid regexp: Trailing backslash: /e</span><span style="color:#b3933a;">\/
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'apple\bake\cake' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk -F</span><span style="color:#d07711;">'e\\' '{print $2}'
</span><span style="color:#5597d6;">gawk:</span><span> fatal: invalid regexp: Trailing backslash: /e</span><span style="color:#b3933a;">\/
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'apple\bake\cake' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk -F</span><span style="color:#d07711;">'e\\\' '{print $2}'
</span><span style="color:#5597d6;">bak
</span></code></pre>
<p>The value assigned to <code>FS</code> is treated as a string and then converted to a regexp. <code>\</code> is a metacharacter for string and regexp both. So, <code>\\</code> in a string means a single backslash and <code>\\\\</code> means double backslash. Double backslash in regexp means a single backslash.</p>
<p><strong>Conclusion</strong>: For a consistent behavior across both <code>mawk</code> and <code>gawk</code> and irrespective of trailing backslash errors, you need to use 4 backslashes for every backslash.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># both 2 and 4 backslashes here gets treated as single backslash
</span><span style="color:#7f8989;"># hence the empty fields in the output
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'1\\2\\3' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">mawk -F</span><span style="color:#d07711;">'\\'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">1,,2,,3
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'1\\2\\3' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">mawk -F</span><span style="color:#d07711;">'\\\\'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">1,,2,,3
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'1\\2\\3' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk -F</span><span style="color:#d07711;">'\\'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">1,,2,,3
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'1\\2\\3' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk -F</span><span style="color:#d07711;">'\\\\'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">1,,2,,3
</span><span>
</span><span style="color:#7f8989;"># 5-8 backslashes give expected results
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'1\\2\\3' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">mawk -F</span><span style="color:#d07711;">'\\\\\'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">1,2,3
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'1\\2\\3' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">mawk -F</span><span style="color:#d07711;">'\\\\\\'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">1,2,3
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'1\\2\\3' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">mawk -F</span><span style="color:#d07711;">'\\\\\\\'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">1,2,3
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'1\\2\\3' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">mawk -F</span><span style="color:#d07711;">'\\\\\\\\'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">1,2,3
</span><span>
</span><span style="color:#7f8989;"># 5-6 backslashes give error, 7-8 backslashes give expected results
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'1\\2\\3' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk -F</span><span style="color:#d07711;">'\\\\\'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">gawk:</span><span> fatal: invalid regexp: Trailing backslash: /</span><span style="color:#b3933a;">\\\/
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'1\\2\\3' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk -F</span><span style="color:#d07711;">'\\\\\\'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">gawk:</span><span> fatal: invalid regexp: Trailing backslash: /</span><span style="color:#b3933a;">\\\/
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'1\\2\\3' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk -F</span><span style="color:#d07711;">'\\\\\\\'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">1,2,3
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'1\\2\\3' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk -F</span><span style="color:#d07711;">'\\\\\\\\'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">1,2,3
</span></code></pre>
<p>As an alternate method, you can use codepoint of the backslash character. This removes one level of escaping. See <a href="https://ascii.cl/">ASCII code table</a> for codepoint reference.</p>
<p><strong>Conclusion</strong>: You need <code>\x5c\x5c</code> for every backslash.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'apple\bake\cake' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">mawk -F</span><span style="color:#d07711;">'e\x5c\x5c' '{print $2}'
</span><span style="color:#5597d6;">bak
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'apple\bake\cake' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk -F</span><span style="color:#d07711;">'e\x5c\x5c' '{print $2}'
</span><span style="color:#5597d6;">bak
</span><span>
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'1\\2\\3' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">mawk -F</span><span style="color:#d07711;">'\x5c\x5c\x5c\x5c'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">1,2,3
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'1\\2\\3' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk -F</span><span style="color:#d07711;">'\x5c\x5c\x5c\x5c'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">1,2,3
</span></code></pre>
<br>
<h2 id="using-awk-to-generate-an-escaped-string">Using awk to generate an escaped string<a class="zola-anchor" href="#using-awk-to-generate-an-escaped-string" aria-label="Anchor link for: using-awk-to-generate-an-escaped-string">🔗</a></h2>
<p>Suppose you want to use <code>\.</code> literally for field splitting. Here's some ways to do it that works for both <code>mawk</code> and <code>gawk</code>:</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'x\2\.y\.z' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk -F</span><span style="color:#d07711;">'\\\\\\.'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">x</span><span style="color:#b3933a;">\2</span><span style="color:#5597d6;">,y,z
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'x\2\.y\.z' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk -F</span><span style="color:#d07711;">'\\\\[.]'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">x</span><span style="color:#b3933a;">\2</span><span style="color:#5597d6;">,y,z
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'x\2\.y\.z' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk -F</span><span style="color:#d07711;">'\x5c\x5c[.]'</span><span style="color:#5597d6;"> -v</span><span> OFS=, </span><span style="color:#d07711;">'{$1=$1} 1'
</span><span style="color:#5597d6;">x</span><span style="color:#b3933a;">\2</span><span style="color:#5597d6;">,y,z
</span></code></pre>
<p>Now, the task is to generate one of the above strings passed to the <code>-F</code> option from <code>\.</code> as input. Using <code>sed</code> is better, but for <a href="https://github.com/learnbyexample/regexp-cut">rcut</a>, I didn't want to add another external tool.</p>
<h3 id="case-1-backslash-madness">Case 1: backslash madness<a class="zola-anchor" href="#case-1-backslash-madness" aria-label="Anchor link for: case-1-backslash-madness">🔗</a></h3>
<p>You need to convert <code>\</code> to 4 backslashes and escape regexp metacharacters with 2 backslashes. Note that you cannot escape all characters except <code>\</code> with 2 backslashes, for example <code>\\t</code> will become a tab character! Also, you need to escape <code>\</code> first and then escape the other metacharacters.</p>
<p>Ready for the solution? I'm not even going to try explaining this, found it by experimenting.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># replacement string for the first gsub has 16 backslashes
</span><span style="color:#7f8989;"># replacement string for the second gsub has 8 backslashes
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'a.b\c^d' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk </span><span style="color:#d07711;">'{gsub(/\\/, "\\\\\\\\\\\\\\\\");
</span><span style="color:#d07711;"> gsub(/[{[(^$*?+.|]/, "\\\\\\\\&")} 1'
</span><span style="color:#5597d6;">a</span><span style="color:#b3933a;">\\</span><span style="color:#5597d6;">.b</span><span style="color:#b3933a;">\\\\</span><span style="color:#5597d6;">c</span><span style="color:#b3933a;">\\</span><span style="color:#5597d6;">^d
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> <img src="/images/warning.svg" alt="warning" /> <a href="https://www.gnu.org/software/gawk/manual/gawk.html#Gory-Details">gawk manual: Gory details</a> might help you understand the above solution.</p>
<h3 id="case-2-character-class">Case 2: character class<a class="zola-anchor" href="#case-2-character-class" aria-label="Anchor link for: case-2-character-class">🔗</a></h3>
<p>One of the characteristic of character class is that you can enclose all characters except <code>\</code> and <code>^</code> to match them literally. The <code>\</code> character is special both inside/outside of character class and <code>[^]</code> is invalid since <code>^</code> is special if used as the first character.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'a.b\c^d' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk </span><span style="color:#d07711;">'{gsub(/\\/, "\\\\\\\\\\\\\\\\");
</span><span style="color:#d07711;"> gsub(/[^^\\]/, "[&]");
</span><span style="color:#d07711;"> gsub(/\^/, "\\\\^")} 1'
</span><span style="color:#5597d6;">[a][.][b]</span><span style="color:#b3933a;">\\\\</span><span style="color:#5597d6;">[c]</span><span style="color:#b3933a;">\\</span><span style="color:#5597d6;">^[d]
</span></code></pre>
<h3 id="case-3-codepoint-to-represent-backslash">Case 3: codepoint to represent backslash<a class="zola-anchor" href="#case-3-codepoint-to-represent-backslash" aria-label="Anchor link for: case-3-codepoint-to-represent-backslash">🔗</a></h3>
<p>Finally, my preferred solutions that uses codepoint instead of escaping backslashes.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#7f8989;"># case 1 alternate
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'a.b\c^d' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk </span><span style="color:#d07711;">'{gsub(/\\/, "\\x5c\\x5c");
</span><span style="color:#d07711;"> gsub(/[{[(^$*?+.|]/, "\\x5c&")} 1'
</span><span style="color:#5597d6;">a</span><span style="color:#b3933a;">\x</span><span style="color:#5597d6;">5c.b</span><span style="color:#b3933a;">\x</span><span style="color:#5597d6;">5c</span><span style="color:#b3933a;">\x</span><span style="color:#5597d6;">5cc</span><span style="color:#b3933a;">\x</span><span style="color:#5597d6;">5c^d
</span><span>
</span><span style="color:#7f8989;"># case 2 alternate
</span><span style="color:#5597d6;">$</span><span> echo </span><span style="color:#d07711;">'a.b\c^d' </span><span style="color:#72ab00;">| </span><span style="color:#5597d6;">gawk </span><span style="color:#d07711;">'{gsub(/[^^\\]/, "[&]");
</span><span style="color:#d07711;"> gsub(/\\/, "\\x5c\\x5c");
</span><span style="color:#d07711;"> gsub(/\^/, "\\x5c^")} 1'
</span><span style="color:#5597d6;">[a][.][b]</span><span style="color:#b3933a;">\x</span><span style="color:#5597d6;">5c</span><span style="color:#b3933a;">\x</span><span style="color:#5597d6;">5c[c]</span><span style="color:#b3933a;">\x</span><span style="color:#5597d6;">5c^[d]
</span></code></pre>
<br>
<h2 id="sanity-check">Sanity check<a class="zola-anchor" href="#sanity-check" aria-label="Anchor link for: sanity-check">🔗</a></h2>
<p>I probably lost my sanity trying to come up with a solution and again while writing this post. I did try a few sanity checks for the solutions presented here, but there's a chance I messed up or missed some corner case. If you spot an issue, do let me know.</p>
Debug woes 2: unexpected array in replacement string2021-06-17T00:00:00+00:002021-06-17T00:00:00+00:00https://learnbyexample.github.io/mini/debug-woes-2/<p>So, I was editing a markdown file in Vim and I wanted to convert some lines to links. The regexp pattern ended up needing non-greedy quantifier, but it didn't work. I thought I got Vim's rather weird <code>\{-}</code> syntax wrong and switched to using Perl from the command line instead of checking the documentation if I had actually made that mistake.</p>
<p>Turns out I made other mistakes in the regexp, but I didn't want to switch back to Vim. I was still scratching my head though, since I wasn't getting the expected output. Thankfully, compared to the <a href="https://learnbyexample.github.io/mini/debug-woes-1/">previous debug misery</a>, I was able to guess this issue soon enough.</p>
<p>Here's a simplified issue, how I debugged it and the corrected usage:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># sample input
</span><span>$ cat ip.txt
</span><span style="color:#72ab00;">*</span><span> blah blah </span><span style="color:#b3933a;">12</span><span> xyz </span><span style="color:#b3933a;">34</span><span> abcd </span><span style="color:#b3933a;">56</span><span> foobaz
</span><span style="color:#72ab00;">-</span><span> blah </span><span style="color:#b3933a;">100</span><span> apple </span><span style="color:#b3933a;">200</span><span> fig
</span><span>
</span><span style="color:#7f8989;"># where I got stuck
</span><span style="color:#7f8989;"># what happened to $1 and $2?
</span><span>$ perl </span><span style="color:#72ab00;">-</span><span>lpe </span><span style="color:#d07711;">'s/^(. )(.*?\d+) (.+)/$1[$2](#$3)/'</span><span> ip.txt
</span><span>(</span><span style="color:#7f8989;">#xyz 34 abcd 56 foobaz)
</span><span>(</span><span style="color:#7f8989;">#apple 200 fig)
</span><span>
</span><span style="color:#7f8989;"># what I did to debug
</span><span style="color:#7f8989;"># step 1: only $1 in the replacement
</span><span>$ perl </span><span style="color:#72ab00;">-</span><span>lpe </span><span style="color:#d07711;">'s/^(. )(.*?\d+) (.+)/$1/'</span><span> ip.txt
</span><span style="color:#72ab00;">*
</span><span style="color:#72ab00;">-
</span><span style="color:#7f8989;"># step 2: $1 and $2 in the replacement
</span><span style="color:#7f8989;"># only empty lines as output - bingo!
</span><span>$ perl </span><span style="color:#72ab00;">-</span><span>lpe </span><span style="color:#d07711;">'s/^(. )(.*?\d+) (.+)/$1[$2]/'</span><span> ip.txt
</span><span>
</span><span>
</span><span style="color:#7f8989;"># $1[$2] treated as array syntax in Perl
</span><span style="color:#7f8989;"># so, need to escape [ since array isn't intended here
</span><span>$ perl </span><span style="color:#72ab00;">-</span><span>lpe </span><span style="color:#d07711;">'s/^(. )(.*?\d+) (.+)/$1\[$2](#$3)/'</span><span> ip.txt
</span><span style="color:#72ab00;">* </span><span>[blah blah </span><span style="color:#b3933a;">12</span><span>](</span><span style="color:#7f8989;">#xyz 34 abcd 56 foobaz)
</span><span style="color:#72ab00;">- </span><span>[blah </span><span style="color:#b3933a;">100</span><span>](</span><span style="color:#7f8989;">#apple 200 fig)
</span></code></pre>
Dreaming solutions2021-06-10T00:00:00+00:002021-06-10T00:00:00+00:00https://learnbyexample.github.io/mini/dreaming-solutions/<p><a href="https://stackoverflow.com/q/67886163/4082052">This SO question</a> was interesting and had various approaches to solve it. Here's a sample example to explain the problem to be solved:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat ip.txt
</span><span>caller_number=</span><span style="color:#b3933a;">034082394234324</span><span>, clear_number=</span><span style="color:#b3933a;">33335345435</span><span>, direction=</span><span style="color:#b3933a;">1</span><span>,
</span><span>caller_number=</span><span style="color:#b3933a;">83479234234</span><span>, clear_number=</span><span style="color:#b3933a;">34836424733</span><span>, direction=</span><span style="color:#b3933a;">2</span><span>,
</span><span>caller_number=</span><span style="color:#b3933a;">83479234234</span><span>, clear_number=</span><span style="color:#b3933a;">64237384533</span><span>, direction=</span><span style="color:#b3933a;">2</span><span>,
</span><span>
</span><span>$ cat list.txt
</span><span style="color:#b3933a;">642
</span><span style="color:#b3933a;">3333
</span><span style="color:#b3933a;">534234235
</span><span>
</span><span>$ cat op.txt
</span><span>caller_number=</span><span style="color:#b3933a;">83479234234</span><span>, clear_number=</span><span style="color:#b3933a;">64237384533</span><span>, direction=</span><span style="color:#b3933a;">2</span><span>,
</span></code></pre>
<p>Any data present in <code>list.txt</code> has to be matched immediately after <code>clear_number=</code> and the input line should also have <code>direction=2,</code>. In the sample above, first line matches <code>3333</code> but not the second criteria. The second line fails even though it has <code>642</code> since it is not immediately after <code>clear_number=</code>. The <code>list.txt</code> file can have 10K-50K lines and <code>ip.txt</code> is around 10GB.</p>
<p>Here's a slightly modified answer based on existing solutions on that thread. Since the data present in <code>list.txt</code> has to be partially matched after <code>clear_number=</code>, a <em>single</em> direct comparison with the keys saved in <code>arr</code> is not possible. This solution loops over all the keys for every input line that matches the <code>direction=2,</code> criteria (breaks the loop if a match is found early).</p>
<pre data-lang="awk" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-awk "><code class="language-awk" data-lang="awk"><span style="color:#5597d6;">FNR</span><span style="color:#72ab00;">==</span><span style="color:#5597d6;">NR</span><span>{ </span><span style="color:#5597d6;">arr</span><span style="color:#72ab00;">[</span><span style="color:#d07711;">"=" </span><span style="color:#5597d6;">$0</span><span style="color:#72ab00;">]</span><span>; </span><span style="color:#72ab00;">next</span><span> }
</span><span>
</span><span style="color:#5597d6;">$3</span><span style="color:#72ab00;">==</span><span style="color:#d07711;">"direction=2,"</span><span>{
</span><span> </span><span style="color:#72ab00;">for</span><span>(</span><span style="color:#5597d6;">i </span><span style="color:#72ab00;">in </span><span style="color:#5597d6;">arr</span><span>)
</span><span> </span><span style="color:#c23f31;">if</span><span>(</span><span style="color:#b39f04;">index</span><span>(</span><span style="color:#5597d6;">$2</span><span>,</span><span style="color:#5597d6;">i</span><span>)){
</span><span> </span><span style="color:#72ab00;">print
</span><span> </span><span style="color:#72ab00;">next
</span><span> }
</span><span>}
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> <em>To run the solutions, use <code>mawk -f script.awk list.txt ip.txt</code></em></p>
<p><strong>In my dreams that night</strong>, I realized that the solution can be improved drastically by looping over the digits after <code>clear_number=</code> instead of looping over keys saved in <code>arr</code>. Matching a key is <code>O(1)</code>, so the time saving is huge since the inner loop is now a maximum of 12 (length of digits after <code>clear_number=</code>) instead of looping a maximum of 10K-50K times! With a 35M sample input file and 12K keys that I created for testing, I found this solution to be about <strong>200</strong> times faster.</p>
<pre data-lang="awk" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-awk "><code class="language-awk" data-lang="awk"><span style="color:#5597d6;">FNR</span><span style="color:#72ab00;">==</span><span style="color:#5597d6;">NR</span><span>{ </span><span style="color:#5597d6;">arr</span><span style="color:#72ab00;">[</span><span style="color:#5597d6;">$0</span><span style="color:#72ab00;">]</span><span>; </span><span style="color:#72ab00;">next</span><span> }
</span><span>
</span><span style="color:#5597d6;">$3</span><span style="color:#72ab00;">==</span><span style="color:#d07711;">"direction=2,"</span><span>{
</span><span> </span><span style="color:#5597d6;">val</span><span style="color:#72ab00;">=</span><span style="color:#b39f04;">substr</span><span>(</span><span style="color:#5597d6;">$2</span><span>,</span><span style="color:#b3933a;">14</span><span>)
</span><span> </span><span style="color:#c23f31;">for</span><span>(</span><span style="color:#5597d6;">i</span><span style="color:#72ab00;">=</span><span style="color:#b3933a;">1</span><span>; </span><span style="color:#5597d6;">i</span><span style="color:#72ab00;"><</span><span style="color:#b39f04;">length</span><span>(</span><span style="color:#5597d6;">val</span><span>); </span><span style="color:#5597d6;">i</span><span style="color:#72ab00;">++</span><span>)
</span><span> </span><span style="color:#c23f31;">if</span><span>(</span><span style="color:#b39f04;">substr</span><span>(</span><span style="color:#5597d6;">val</span><span>,</span><span style="color:#b3933a;">1</span><span>,</span><span style="color:#5597d6;">i</span><span>) </span><span style="color:#72ab00;">in </span><span style="color:#5597d6;">arr</span><span>){
</span><span> </span><span style="color:#72ab00;">print
</span><span> </span><span style="color:#72ab00;">next
</span><span> }
</span><span>}
</span></code></pre>
GNU BRE/ERE cheatsheet and differences between grep, sed and awk2021-05-31T00:00:00+00:002022-08-17T00:00:00+00:00https://learnbyexample.github.io/gnu-bre-ere-cheatsheet/<p align="center"><img src="/images/bre_ere_cheatsheet.png" alt="GNU BRE/ERE cheatsheet" /></p>
<p align="center"><i>Poster created using <a href="https://www.canva.com/">Canva</a></i></p>
<p>This post covers <strong>Basic Regular Expressions</strong> (BRE) and <strong>Extended Regular Expressions</strong> (ERE) syntax supported by GNU <code>grep</code>, <code>sed</code> and <code>awk</code>. You'll also learn the differences between these tools — for example, <code>awk</code> doesn't support backreferences within regexp definition (i.e. the search portion).</p>
<span id="continue-reading"></span><br>
<h2 id="bre-and-ere">BRE and ERE<a class="zola-anchor" href="#bre-and-ere" aria-label="Anchor link for: bre-and-ere">🔗</a></h2>
<p><img src="/images/info.svg" alt="info" /> From <a href="https://www.gnu.org/software/grep/manual/grep.html#Basic-vs-Extended">GNU grep manual</a>:</p>
<blockquote>
<p>In basic regular expressions the meta-characters <code>?</code>, <code>+</code>, <code>{</code>, <code>|</code>, <code>(</code>, and <code>)</code> lose their special meaning; instead use the backslashed versions <code>\?</code>, <code>\+</code>, <code>\{</code>, <code>\|</code>, <code>\(</code>, and <code>\)</code>.</p>
</blockquote>
<p><code>grep</code> and <code>sed</code> support BRE by default and enables ERE when <code>-E</code> option is used. <code>awk</code> supports only ERE. Assume ERE for descriptions in this post unless otherwise mentioned.</p>
<p>This post is intended as a reference for BRE/ERE flavor of regular expressions. For a more detailed explanation with examples and exercises, see these chapters from <a href="https://learnbyexample.github.io/books/">my ebooks</a>:</p>
<ul>
<li><a href="https://learnbyexample.github.io/learn_gnugrep_ripgrep/breere-regular-expressions.html">grep BRE/ERE Regular Expressions</a></li>
<li><a href="https://learnbyexample.github.io/learn_gnused/breere-regular-expressions.html">sed BRE/ERE Regular Expressions</a></li>
<li><a href="https://learnbyexample.github.io/learn_gnuawk/regular-expressions.html">awk Regular Expressions</a></li>
</ul>
<br>
<h2 id="anchors">Anchors<a class="zola-anchor" href="#anchors" aria-label="Anchor link for: anchors">🔗</a></h2>
<table><thead><tr><th>Pattern</th><th>Description</th></tr></thead><tbody>
<tr><td><code>^</code></td><td>restricts the match to the start of the string</td></tr>
<tr><td><code>$</code></td><td>restricts the match to the end of the string</td></tr>
<tr><td><code>\<</code></td><td>restricts the match to the start of word</td></tr>
<tr><td><code>\></code></td><td>restricts the match to the end of word</td></tr>
</tbody></table>
<p>The <code>-x</code> cli option in <code>grep</code> is equivalent to <code>^pattern$</code>.</p>
<p>Word characters include alphabets, digits and underscore. Here's some more alternate ways to specify word anchors:</p>
<table><thead><tr><th>Pattern</th><th>Description</th></tr></thead><tbody>
<tr><td><code>\b</code></td><td>restricts the match to the start/end of words, applicable for <code>grep</code> and <code>sed</code></td></tr>
<tr><td><code>\y</code></td><td>restricts the match to the start/end of words, applicable for <code>awk</code> (<code>\b</code> means backspace)</td></tr>
<tr><td><code>\B</code></td><td>matches wherever <code>\b</code> (or <code>\y</code>) doesn't match</td></tr>
</tbody></table>
<p><code>grep</code> also supports <code>-w</code> cli option. It is equivalent to <code>(?<!\w)pattern(?!\w)</code>. The three different ways to specify word anchors are not exactly equivalent though, see <a href="https://learnbyexample.github.io/learn_gnugrep_ripgrep/gotchas-and-tricks.html#word-boundary-differences">Word boundary differences</a> section from my book for details and examples.</p>
<br>
<h2 id="alternation-and-grouping">Alternation and Grouping<a class="zola-anchor" href="#alternation-and-grouping" aria-label="Anchor link for: alternation-and-grouping">🔗</a></h2>
<table><thead><tr><th>Pattern</th><th>Description</th></tr></thead><tbody>
<tr><td><code>pat1|pat2|pat3</code></td><td>match <code>pat1</code> or <code>pat2</code> or <code>pat3</code></td></tr>
<tr><td></td><td>use <code>\|</code> in BRE mode</td></tr>
<tr><td><code>()</code></td><td>group pattern(s), <code>a(b|c)d</code> is same as <code>abd|acd</code></td></tr>
<tr><td></td><td>use <code>\(\)</code> in BRE mode</td></tr>
</tbody></table>
<p>The alternative patterns can have their own independent anchors. Alternative which matches earliest in the input gets precedence. Longest matching portion wins if multiple alternatives start from the same location (irrespective of the order of alternatives). In case of a tie with same lengths, leftmost alternative wins (see <a href="https://stackoverflow.com/a/39752929/4082052">stackoverflow: Non greedy matching in sed</a> for a practical use case).</p>
<br>
<h2 id="escaping-metacharacters">Escaping metacharacters<a class="zola-anchor" href="#escaping-metacharacters" aria-label="Anchor link for: escaping-metacharacters">🔗</a></h2>
<table><thead><tr><th>Pattern</th><th>Description</th></tr></thead><tbody>
<tr><td><code>\</code></td><td>prefix metacharacters with <code>\</code> to match them literally</td></tr>
<tr><td><code>\\</code></td><td>to match <code>\</code> literally</td></tr>
</tbody></table>
<ul>
<li>With <code>grep</code> and <code>sed</code>, switching between ERE and BRE can reduce the number of escapes needed for some cases. For fixed string matching, <code>grep</code> has <code>-F</code> option and <code>awk</code> has string comparison operators (whole string) and the <code>index</code> function (partial string).</li>
<li><code>sed</code> requires both <code>(</code> and <code>)</code> characters to be escaped (in ERE mode), whereas <code>grep</code> and <code>awk</code> don't require <code>)</code> to be escaped.</li>
<li><code>sed</code> requires <code>{</code> to be escaped (in ERE mode) even if it isn't part of a valid quantifier syntax, whereas <code>grep</code> and <code>awk</code> don't require escaping. For example, you'd need <code>\{a}</code> in <code>sed</code> whereas <code>{a}</code> is enough for the other two.</li>
<li>In BRE mode, <code>grep</code> and <code>sed</code> don't require <code>^</code> and <code>$</code> to be escaped if they are used away from their customary positions.</li>
</ul>
<br>
<h2 id="dot-metacharacter-and-quantifiers">Dot metacharacter and Quantifiers<a class="zola-anchor" href="#dot-metacharacter-and-quantifiers" aria-label="Anchor link for: dot-metacharacter-and-quantifiers">🔗</a></h2>
<table><thead><tr><th>Pattern</th><th>Description</th></tr></thead><tbody>
<tr><td><code>.</code></td><td>match any character, including the newline character</td></tr>
<tr><td><code>?</code></td><td>match <code>0</code> or <code>1</code> times</td></tr>
<tr><td></td><td>use <code>\?</code> in BRE mode</td></tr>
<tr><td><code>*</code></td><td>match <code>0</code> or more times</td></tr>
<tr><td><code>+</code></td><td>match <code>1</code> or more times</td></tr>
<tr><td></td><td>use <code>\+</code> in BRE mode</td></tr>
<tr><td><code>{m,n}</code></td><td>match <code>m</code> to <code>n</code> times</td></tr>
<tr><td><code>{m,}</code></td><td>match at least <code>m</code> times</td></tr>
<tr><td><code>{,n}</code></td><td>match up to <code>n</code> times (including <code>0</code> times)</td></tr>
<tr><td><code>{n}</code></td><td>match exactly <code>n</code> times</td></tr>
<tr><td></td><td>use <code>\{\}</code> in BRE mode</td></tr>
<tr><td><code>pat1.*pat2</code></td><td>any number of characters between <code>pat1</code> and <code>pat2</code></td></tr>
<tr><td><code>pat1.*pat2|pat2.*pat1</code></td><td>match both <code>pat1</code> and <code>pat2</code> in any order</td></tr>
</tbody></table>
<p>Precedence rule is <em>longest match wins</em>, which is mostly similar but not exactly same as greedy quantifiers. For example, with <code>foo123312baz</code> as input string, <code>o[123]+(12baz)?</code> will match <code>o123312baz</code> with these tools, whereas it will match <code>o123312</code> with greedy quantifiers.</p>
<br>
<h2 id="character-class">Character class<a class="zola-anchor" href="#character-class" aria-label="Anchor link for: character-class">🔗</a></h2>
<table><thead><tr><th>Pattern</th><th>Description</th></tr></thead><tbody>
<tr><td><code>[set123]</code></td><td>match any of these characters once</td></tr>
<tr><td><code>[^set123]</code></td><td>match except any of these characters once</td></tr>
<tr><td><code>[3-7AM-X]</code></td><td>range of characters from <code>3</code> to <code>7</code>, <code>A</code>, another range from <code>M</code> to <code>X</code></td></tr>
<tr><td><code>[.</code></td><td>open collating symbol</td></tr>
<tr><td><code>.]</code></td><td>close collating symbol</td></tr>
<tr><td><code>[=</code></td><td>open equivalence class</td></tr>
<tr><td><code>=]</code></td><td>close equivalence class</td></tr>
</tbody></table>
<p>Specific placement will help to match character class metacharacters literally.</p>
<table><thead><tr><th>Pattern</th><th>Description</th></tr></thead><tbody>
<tr><td><code>[a-z-]</code></td><td><code>-</code> should be first/last character to match literally</td></tr>
<tr><td><code>[+^]</code></td><td><code>^</code> shouldn't be first character</td></tr>
<tr><td><code>[]=]</code></td><td><code>]</code> should be first character (second if <code>^</code> is used to invert the set)</td></tr>
</tbody></table>
<ul>
<li><code>\</code> isn't special within character class in <code>grep</code>.</li>
<li><code>\</code> can be used to escape character class metacharacters in <code>awk</code>.</li>
</ul>
<p>Some commonly used character sets have predefined escape sequences:</p>
<table><thead><tr><th>Pattern</th><th>Description</th></tr></thead><tbody>
<tr><td><code>\w</code></td><td>similar to <code>[a-zA-Z0-9_]</code> for matching word characters</td></tr>
<tr><td><code>\s</code></td><td>similar to <code>[ \t\n\r\f\v]</code> for matching whitespace characters</td></tr>
<tr><td><code>\W</code></td><td>match non-word characters</td></tr>
<tr><td><code>\S</code></td><td>match non-whitespace characters</td></tr>
</tbody></table>
<ul>
<li>Undefined escape sequences will be treated as the character it escapes. For example, <code>\e</code> will match <code>e</code> (not <code>\</code> and <code>e</code>).
<ul>
<li>in addition, <code>awk</code> gives a "not a known regexp operator" warning.</li>
</ul>
</li>
<li>The above escape sequences <em>cannot</em> be used inside character classes and behavior varies between the tools.
<ul>
<li>For example, using <code>[\w]</code> will match <code>\</code> or <code>w</code> characters in <code>grep</code> and <code>sed</code> whereas it will match only <code>w</code> in <code>awk</code>.</li>
</ul>
</li>
<li>These escape sequences are also locale aware, for example <code>αλεπού</code> and <code>\u2028</code> (line separator) will be considered as word and whitespace characters respectively in appropriate locales.</li>
<li>These tools do <em>not</em> support <code>\d</code> and <code>\D</code>, commonly featured in other regexp implementations for digits and non-digits.</li>
</ul>
<br>
<h2 id="escape-sequences">Escape sequences<a class="zola-anchor" href="#escape-sequences" aria-label="Anchor link for: escape-sequences">🔗</a></h2>
<p>This section is applicable only for <code>sed</code> and <code>awk</code> unless otherwise specified and can be used within character classes too. See also <a href="https://ascii.cl/">ASCII Codes Table Standard characters</a>.</p>
<table><thead><tr><th>Escape sequence</th><th>Description</th></tr></thead><tbody>
<tr><td><code>\a</code></td><td>alert</td></tr>
<tr><td><code>\b</code></td><td>backspace in <code>awk</code>, word boundary in <code>grep</code> and <code>sed</code></td></tr>
<tr><td></td><td><code>\b</code> inside a character class in <code>sed</code> will act as a backspace</td></tr>
<tr><td><code>\f</code></td><td>formfeed</td></tr>
<tr><td><code>\n</code></td><td>newline</td></tr>
<tr><td><code>\r</code></td><td>carriage return</td></tr>
<tr><td><code>\t</code></td><td>horizontal tab</td></tr>
<tr><td><code>\v</code></td><td>vertical tab</td></tr>
<tr><td><code>\cx</code></td><td>CONTROL-x in <code>sed</code></td></tr>
</tbody></table>
<p>You can also represent ASCII characters using their codepoint values.</p>
<table><thead><tr><th>Escape sequence</th><th>Description</th></tr></thead><tbody>
<tr><td><code>\xNN</code></td><td>hexadecimal digits</td></tr>
<tr><td><code>\NNN</code></td><td>octal digits in <code>awk</code></td></tr>
<tr><td><code>\oNNN</code></td><td>octal digits in <code>sed</code></td></tr>
<tr><td><code>\dNNN</code></td><td>decimal digits in <code>sed</code></td></tr>
</tbody></table>
<ul>
<li>In search section, a metacharacter specified by escape sequences will still act as the metacharacter. For example, <code>/\x5eco/</code> will match <code>co</code> only at the start of the string.</li>
<li>In replacement section,
<ul>
<li>escape sequences in <code>sed</code> produces literal character. For example, <code>s/.*/"\x26"/</code> will have <code>"&"</code> as the replacement value.</li>
<li>escape sequences in <code>awk</code> is treated as metacharacter. For example, <code>sub(/.*/, "[&]")</code> and <code>sub(/.*/, "[\x26]")</code> are equivalent.</li>
</ul>
</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> Ways to use escape sequences with <code>grep</code>:</p>
<ul>
<li><a href="https://www.gnu.org/software/bash/manual/bash.html#ANSI_002dC-Quoting">ANSI-C Quoting</a> — for example, <code>$'a\tb'</code> will match <code>a</code> and <code>b</code> with a tab in between.</li>
<li><code>-P</code> option, see my chapter on <a href="https://learnbyexample.github.io/learn_gnugrep_ripgrep/perl-compatible-regular-expressions.html">Perl Compatible Regular Expressions</a> for more details.</li>
</ul>
<br>
<h2 id="named-character-sets">Named character sets<a class="zola-anchor" href="#named-character-sets" aria-label="Anchor link for: named-character-sets">🔗</a></h2>
<p>The below table lists named sets and their equivalent character class in ASCII encoding. These can be used inside character classes only. For example, <code>[[:digit:]]</code> is same as <code>[0-9]</code> and <code>[[:alnum:]_]</code> is equivalent to <code>\w</code>.</p>
<table><thead><tr><th>Named set</th><th>Description</th></tr></thead><tbody>
<tr><td><code>[:digit:]</code></td><td><code>[0-9]</code></td></tr>
<tr><td><code>[:lower:]</code></td><td><code>[a-z]</code></td></tr>
<tr><td><code>[:upper:]</code></td><td><code>[A-Z]</code></td></tr>
<tr><td><code>[:alpha:]</code></td><td><code>[a-zA-Z]</code></td></tr>
<tr><td><code>[:alnum:]</code></td><td><code>[0-9a-zA-Z]</code></td></tr>
<tr><td><code>[:xdigit:]</code></td><td><code>[0-9a-fA-F]</code></td></tr>
<tr><td><code>[:cntrl:]</code></td><td>control characters — first 32 ASCII characters and 127th (DEL)</td></tr>
<tr><td><code>[:punct:]</code></td><td>all the punctuation characters</td></tr>
<tr><td><code>[:graph:]</code></td><td><code>[:alnum:]</code> and <code>[:punct:]</code></td></tr>
<tr><td><code>[:print:]</code></td><td><code>[:alnum:]</code>, <code>[:punct:]</code> and space</td></tr>
<tr><td><code>[:blank:]</code></td><td>space and tab characters</td></tr>
<tr><td><code>[:space:]</code></td><td>whitespace characters, same as <code>\s</code></td></tr>
</tbody></table>
<p><img src="/images/info.svg" alt="info" /> From <a href="https://www.gnu.org/software/grep/manual/grep.html#Character-Classes-and-Bracket-Expressions">grep manual</a>:</p>
<blockquote>
<p>Their interpretation depends on the <code>LC_CTYPE</code> locale; for example, <code>[[:alnum:]]</code> means the character class of numbers and letters in the current locale.</p>
</blockquote>
<br>
<h2 id="backreferences">Backreferences<a class="zola-anchor" href="#backreferences" aria-label="Anchor link for: backreferences">🔗</a></h2>
<table><thead><tr><th>Pattern</th><th>Description</th></tr></thead><tbody>
<tr><td><code>\N</code></td><td>backreference, gives matched portion of Nth capture group</td></tr>
<tr><td></td><td>possible values: <code>\1</code>, <code>\2</code> up to <code>\9</code></td></tr>
<tr><td><code>&</code></td><td>represents entire matched string in the replacement section</td></tr>
<tr><td><code>\0</code></td><td>equivalent to <code>&</code> in <code>sed</code></td></tr>
</tbody></table>
<p>Notes for <code>awk</code>:</p>
<ul>
<li>backreferences can be used only in replacement section, not allowed in search section.</li>
<li><code>sub</code> and <code>gsub</code> functions allow only the <code>&</code> backreference.</li>
<li><code>gensub</code> function allows <code>\N</code> form of backreference as well.
<ul>
<li>but need to use <code>\\0</code>, <code>\\1</code>, <code>\\2</code> etc since they are specified using string syntax.</li>
</ul>
</li>
</ul>
<br>
<h2 id="sed-flags">sed flags<a class="zola-anchor" href="#sed-flags" aria-label="Anchor link for: sed-flags">🔗</a></h2>
<p>This section discusses flags (also known as modifiers) that change the regexp behavior. When used with regexp addressing:</p>
<table><thead><tr><th>Flag</th><th>Description</th></tr></thead><tbody>
<tr><td><code>I</code></td><td>match case insensitively</td></tr>
</tbody></table>
<p>When used with substitution command:</p>
<table><thead><tr><th>Flag</th><th>Description</th></tr></thead><tbody>
<tr><td><code>i</code> or <code>I</code></td><td>match case insensitively</td></tr>
<tr><td><code>g</code></td><td>replace all occurrences instead of just the first match</td></tr>
<tr><td><code>N</code></td><td>a number will cause only the <em>N</em>th match to be replaced</td></tr>
<tr><td><code>Ng</code></td><td>replace from <em>N</em>th match to the end</td></tr>
<tr><td><code>m</code> or <code>M</code></td><td>multiline mode</td></tr>
<tr><td></td><td><code>.</code> will not match the newline character</td></tr>
<tr><td></td><td><code>^</code> and <code>$</code> will match every line's start and end locations (line separator is <code>\n</code> by default and NUL when <code>-z</code> option is used)</td></tr>
<tr><td><code>\`</code></td><td>always match the start of string irrespective of multiline mode</td></tr>
<tr><td><code>\'</code></td><td>always match the end of string irrespective of multiline mode</td></tr>
</tbody></table>
<p>Flags are not supported by <code>grep</code> or <code>awk</code>. But these equivalent/alternative options can be used:</p>
<ul>
<li><code>-i</code> cli option in <code>grep</code> and setting <code>IGNORECASE</code> to non-zero value in <code>awk</code> will match case insensitively.</li>
<li><code>tolower</code> or <code>toupper</code> functions can be used in <code>awk</code> to convert input to single case.</li>
<li>you can also use character classes for small strings, for example <code>[cC][aA][tT]</code> will match <code>cat</code> case insensitively.</li>
<li><code>sub</code> function in <code>awk</code> replaces only the first matching occurrence and <code>gsub</code> function is equivalent to using the <code>g</code> flag.</li>
<li>third argument of <code>gensub</code> function in <code>awk</code> supports replacing only the <em>N</em>th match as well as the <code>g</code> flag.</li>
</ul>
<p>The behavior of <code>sed</code> and <code>awk</code> differs for <em>N</em>th match if the pattern can match empty string:</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ echo </span><span style="color:#d07711;">'a,,c,d,,f' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/[^,]*/b/2'
</span><span>a,b,c,d,,f
</span><span>$ echo </span><span style="color:#d07711;">'a,,c,d,,f' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/[^,]*/e/5'
</span><span>a,,c,d,e,f
</span><span>
</span><span>$ echo </span><span style="color:#d07711;">'a,,c,d,,f' </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#d07711;">'{print gensub(/[^,]*/, "b", 2)}'
</span><span>ab,,c,d,,f
</span><span>$ echo </span><span style="color:#d07711;">'a,,c,d,,f' </span><span style="color:#72ab00;">|</span><span> awk </span><span style="color:#d07711;">'{print gensub(/[^,]*/, "e", 5)}'
</span><span>a,,ce,d,,f
</span></code></pre>
<br>
<h2 id="sed-case-conversion">sed case conversion<a class="zola-anchor" href="#sed-case-conversion" aria-label="Anchor link for: sed-case-conversion">🔗</a></h2>
<table><thead><tr><th>Escape sequence</th><th>Description</th></tr></thead><tbody>
<tr><td><code>\E</code></td><td>indicates end of case conversion in replacement section</td></tr>
<tr><td><code>\l</code></td><td>convert next character to lowercase</td></tr>
<tr><td><code>\u</code></td><td>convert next character to uppercase</td></tr>
<tr><td><code>\L</code></td><td>convert following characters to lowercase, stops if <code>\U</code> or <code>\E</code> is found</td></tr>
<tr><td><code>\U</code></td><td>convert following characters to uppercase, stops if <code>\L</code> or <code>\E</code> is found</td></tr>
</tbody></table>
<br>
<h2 id="sed-delimiters">sed delimiters<a class="zola-anchor" href="#sed-delimiters" aria-label="Anchor link for: sed-delimiters">🔗</a></h2>
<ul>
<li><code>/</code> is idiomatically used as the delimiter.</li>
<li>Any character except <code>\</code> and newline character can also be used. For example: <code>s#/home/learnbyexample/#~/#</code> is same as <code>s/\/home\/learnbyexample\//~\//</code>.</li>
<li>For regexp addressing, the first delimiter has to be escaped. For example: <code>\;/foo/bar/;p</code> is same as <code>/foo\/bar\//p</code>.</li>
</ul>
Debug woes 1: multiple substitutions on the same line2021-05-29T00:00:00+00:002021-05-29T00:00:00+00:00https://learnbyexample.github.io/mini/debug-woes-1/<p>While answering <a href="https://stackoverflow.com/q/67703405/4082052">this SO question</a>, I ran into a debug misery. It took me an embarrassing amount of time and experiments to understand why.</p>
<p>Here's a simplified version of the problem. Can you spot the issue?</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat ip.txt
</span><span>a b c d
</span><span>i j k l
</span><span>
</span><span style="color:#7f8989;"># Change only first two occurrences of spaces with tabs
</span><span>$ perl </span><span style="color:#72ab00;">-</span><span>pe </span><span style="color:#d07711;">'$c=2; s/ /\t/ while $c--'</span><span> ip.txt
</span><span>a b c d
</span><span>i j k l
</span><span>
</span><span style="color:#7f8989;"># Wanted to generalize the solution to match one-or-more whitespaces
</span><span style="color:#7f8989;"># But it doesn't work!!!
</span><span>$ perl </span><span style="color:#72ab00;">-</span><span>pe </span><span style="color:#d07711;">'$c=2; s/\s+/\t/ while $c--'</span><span> ip.txt
</span><span>a b c d
</span><span>i j k l
</span></code></pre>
<br>
<details><summary>Click to view answer</summary>
The substitution works from start of the line for every iteration of the while loop. Tab is one of the whitespace characters, so after the first substitution, the tab gets matched for rest of the iterations.
</details>
Perl one-liner articles2021-05-26T00:00:00+00:002021-05-26T00:00:00+00:00https://learnbyexample.github.io/mini/perl-oneliner-articles/<p>One of the feedback I got for my <a href="https://github.com/learnbyexample/learn_perl_oneliners">Perl one-liners ebook</a> was to showcase examples where Perl shines compared to other text processing tools.</p>
<p>Soon after, I got an invite to publish an article for the <a href="https://www.perl.com/">perldotcom</a> site. So, I wrote a two-part post highlighting use cases where Perl's rich regular expression engine, built-in functions, extensive ecosystem and portability helps:</p>
<ul>
<li><a href="https://www.perl.com/article/perl-one-liners-part-1/">Perl / Unix One-liner Cage Match, Part 1</a></li>
<li><a href="https://www.perl.com/article/perl-one-liners-part-2/">Perl / Unix One-liner Cage Match, Part 2</a></li>
</ul>
<p><em>Thanks to the editors for suggestions and improvements.</em></p>
Paying my bills with 'free' ebooks2021-03-03T00:00:00+00:002023-01-19T00:00:00+00:00https://learnbyexample.github.io/my-book-writing-experience/<p><strong>TL;DR</strong>: Small victories are more precious when you have nothing. Instead of burning through my savings, I'm now adding to it. The relief is priceless.</p>
<span id="continue-reading"></span><br>
<h2 id="it-is-worth-it-for-me">It is worth it (for me)<a class="zola-anchor" href="#it-is-worth-it-for-me" aria-label="Anchor link for: it-is-worth-it-for-me">🔗</a></h2>
<p>The section title is my response to this article <a href="https://martin.kleppmann.com/2020/09/29/is-book-writing-worth-it.html">Writing a book: is it worth it?</a> that I saw on <a href="https://news.ycombinator.com/item?id=24628549">Hacker News</a>.</p>
<p>For my unique circumstances, the decision to write ebooks has brought me financial stability, improved my mental health and gives me a sense of satisfaction. This could've come from any of my previous attempts to earn money, but ebooks is what worked out for me.</p>
<p align="center"><img src="/images/books/book_writing.jpg" alt="Book writing" /></p>
<p align="center"><i>Photo by <a href="https://unsplash.com/@bramnaus">Bram Naus</a> on <a href="https://unsplash.com/photos/n8Qb1ZAkK88">Unsplash</a></i></p>
<br>
<h2 id="how-it-all-started">How it all started?<a class="zola-anchor" href="#how-it-all-started" aria-label="Anchor link for: how-it-all-started">🔗</a></h2>
<p>I left my job in 2014 for various reasons. I didn't have any plans for the future, just knew that I couldn't work as an employee any more.</p>
<p>After enjoying my break, I had to try something to start earning again. I wrote an android gaming app, fantasized earning loads of money with an awesome work planner/communicator software that never left my imaginations, tried a small stint with <a href="https://krishworks.com/">a team making an educational app</a>, etc. I failed due to various reasons — didn't try hard enough, quit early, didn't fit my skills, wasn't good at design/marketing and so on. The educational app for example went on to become a success. Or perhaps, having saved enough to live out a few years without working meant I wasn't under enough pressure to earn.</p>
<p>Among these failures, college workshops was the sole bread giver (and long way from supporting my modest living costs). My bachelor's degree was in electronics and communications and I had worked in a semiconductor company. So I knew enough to teach students pursuing similar courses the basics for Linux command line, Vim, Perl, Bash scripting, etc. As reference materials, I used to provide ppt slides (when I still had a job). Now that I had loads of free time, I started expanding my knowledge. Came to know about sites like Stackoverflow/Stackexchange/Reddit/etc. With newer and better materials to learn from, I created PDFs (using LibreOffice, which was pretty much the only option I knew about).</p>
<p>Another loss maker was getting a domain/host to share these learning materials. Web development was too much for me and the (ugly) site didn't get any love. In hindsight, one of the better turning points was learning about GitHub in 2016. I loved markdown's nice output with syntax highlighting (and realized I was using it poorly in Reddit) and GitHub's social aspect (stars, issues, etc) — plus I can use Vim! I manually converted my materials from LibreOffice to markdown (again, I didn't know that tools like <code>pandoc</code> could've helped me). Just like any other skill, I was learning and getting better with every iteration. That was the year I learned Python (thanks to <a href="https://inventwithpython.com/"><strong>Al Sweigart</strong></a>'s free coupon for "Automate the Boring Stuff with Python" video course) and started conducting workshops for Python instead of Perl.</p>
<p>Being active on Stackoverflow and Reddit, I finally became proficient at CLI one-liners (late by 8 years, since it would have significantly helped in my role as a design and test engineer). I came across articles/books on regular expressions and one-liners. I thought — I can do that too, plus I was really liking them. Thus began my epic <a href="https://github.com/learnbyexample/Command-line-text-processing">Command Line Text Processing</a> repo, another big turning point in my journey as an author.</p>
<br>
<h2 id="encouraging-signs">Encouraging signs<a class="zola-anchor" href="#encouraging-signs" aria-label="Anchor link for: encouraging-signs">🔗</a></h2>
<p>Over the course of ten months, I managed to complete the holy trinity of <code>grep</code>, <code>sed</code> and <code>awk</code> one-liners. I promoted these tutorials on Reddit, Google+, LinkedIn and other social sites I knew at that time. The repo got hundreds of stars and more importantly, I got critical feedback. I was ecstatic, even if I was continuing to burn through my savings.</p>
<p>Then, I got to know about Hacker News (I think it was someone bragging about reaching front page). It took me a while to get used to Reddit, and HN was similarly alien to me. I posted a few links as a test and then I was <a href="https://news.ycombinator.com/item?id=15549318">brave enough to submit</a> my <code>awk</code> one-liners post. I was refreshing HN anxiously for about half an hour or so. It got one vote and then other submissions pushed it away from new posts tab. Disappointed, I moved on. After sometime, I was checking traffic on my GitHub repo as usual, a habit I had picked up (all kinds of points, karma, likes, etc were so enticing). I noticed a HUGE spike in traffic and star count, the likes of which I had never seen before/since. The last time I had felt that proud of my work was during my job. This comment made a big impression on me:</p>
<blockquote>
<p>These are the best stories on HN and why i subscribed here in the first place. I have often seen awk used so many times on SO but I've always put it up for something later to learn. Finally today I have some basic understanding of awk and this is really great stuff! I did get by with Perl but this is definitely more handy and the example approach to teaching it makes is super easy to understand!</p>
</blockquote>
<p>After the euphoria had died down (about a week I guess), I was thinking about all the various kinds of posts I could make. And I was thinking how to use the repo popularity to bring in money. Long story short, I ended up adding donate buttons to my repos. This was before GitHub sponsors was announced. I wanted my materials to be freely available, so I wasn't even thinking about creating paid only options. Despite adding more tutorials, getting featured in <a href="https://rubyweekly.com/issues/389">rubyweekly</a> and other newsletters, social sites, etc, all I got was a single recurring donation (which ended prematurely when that platform switched payment set up).</p>
<p>Another turning point came when a friend of mine was authoring a book and referred me for the reviewer role. Around that time, I had been converting <a href="https://github.com/AllenDowney/ThinkPython2">Think Python</a> to <a href="https://github.com/learnbyexample/ThinkRubyBuild">Think Ruby</a> and simultaneously working on a separate Ruby tutorial. During the book review process, I was given a list of topics and asked if I was interested in writing a book (they were impressed by my existing repos). The topics were either beyond my knowledge or out of scope, and they weren't interested in the repos I had already put up.</p>
<p>My friends were always suggesting me to write a book and my reply consistently had been that I wasn't good enough to write one (the imposter syndrome hasn't left me even now). The book review experience, existing repos, my tryst with Think Ruby, dwindling savings, etc changed my mindset enough to try. By then I was already familiar with Leanpub, so I knew self-publishing was an option. I picked a niche topic (<a href="https://learnbyexample.github.io/Ruby_Regexp/">Ruby Regexp</a>), <a href="https://learnbyexample.github.io/customizing-pandoc/">learned enough <code>pandoc</code> to produce a PDF</a> and published it even before the book review ended. It helped that I already had material as part of the Ruby tutorial I was working on. I still had to work a lot, since tutorial description was all bullet points.</p>
<p>I got only a few sales, but I had landed another review (video course for the same book) and was getting paid. So, I converted 'Ruby Regexp' to <a href="https://learnbyexample.github.io/py_regular_expressions/">Python re(gex)?</a>. I made it free for a few days and posted on Reddit, HN and other social sites. HN submission didn't get any traction, but fortunately <a href="https://www.reddit.com/r/Python/comments/aeusdu/i_wrote_a_book_on_python_regular_expressions_it/">Reddit submission on /r/Python/</a> was a big hit — thousands of free downloads and a few paid ones enough to cover 2 months of my expenses. I should mention now that I live alone, in outskirts of an Indian city, and my modest lifestyle costs about $150 per month. What works for me won't necessarily suit others.</p>
<br>
<h2 id="a-dip-followed-by-sustainable-momentum">A dip followed by sustainable momentum<a class="zola-anchor" href="#a-dip-followed-by-sustainable-momentum" aria-label="Anchor link for: a-dip-followed-by-sustainable-momentum">🔗</a></h2>
<p>Encouraged by the second release, I changed my focus from updating my GitHub repos to writing books. All those repos were now a fodder for book conversion. I picked up <code>grep</code> first and included <code>ripgrep</code> as well to keep it inline with the trend. Got decent sales from <em>free</em> promotions. HN submission tanked at first, but got good attention when I posted again after a revision. Then I published a new version of 'Python re(gex)?' with significant changes and this HN submission got good views too. But note that these HN hits weren't anywhere close to what my <code>awk</code> one-liners post had received.</p>
<p>Writing <code>sed</code> took a lot out of me. Probably I was getting jaded again, juggling between workshops and ebooks. Then I had a medical issue. I didn't even try promoting the <code>sed</code> book on HN. I managed to learn enough JS to write <a href="https://github.com/learnbyexample/learn_js_regexp">JavaScript regexp</a>. Wasn't anywhere close to what I got from the Python book.</p>
<p align="center"><img src="/images/books/roller_coaster.jpg" alt="Roller Coaster" /></p>
<p align="center"><i>Photo by <a href="https://unsplash.com/@davidtrana">David Traña</a> on <a href="https://unsplash.com/photos/mmdchg5UPtQ">Unsplash</a></i></p>
<p>So, despite reasonable reception during free promotions, my ebooks weren't still good enough to consistently pay my bills. Combined with workshops I was just about making my ends meet. I was losing interest and the medical issue was continuing. Still, without anything else to do, I finally started a book on <code>awk</code> one-liners. Things started getting better for a few months and then the pandemic hit.</p>
<p>Given the recent medical scare, pandemic fears and the trend of giveaways, I decided to open source my book contents. And, I made all my ebooks free to download indefinitely. Made a single bundle of all the 5 books I had published until then to make it easier to download in one shot. The reception was better than expected. Shortly after (last week of March), I published the <code>awk</code> book early by cutting corners like excluding exercises. All books bundle now had 6 entries. Again, the reception was much better than expected. I hadn't made so many paid sales during a month ever before.</p>
<p>Encouraged by the success, I made another important decision. Instead of starting another book, I took up the task of updating all my books. I alloted a month or two for this task, but it took me more than 4 months in the end. It wasn't that I had lot of new features to add. The feedback I had received over the past year and my own improving writing skills meant that I just couldn't help updating the books to the best of my abilities. Somehow, lockdown and fear of the pandemic ended up improving my workflow.</p>
<p>Workshops weren't going to come my way anytime soon, but ebook sales for about 6 months averaged $200+ per month. For the first time since leaving my job, I was saving money!!! During this period all my books were free to download, in addition to the markdown source being available from GitHub repos. I even managed to create EPUB and web versions for my ebooks. The web version generated using <a href="https://github.com/rust-lang/mdBook">mdBook</a> was much better than my attempts with wordpress all those years ago, but to be fair I hadn't known enough about formatting for coding books.</p>
<p>After finishing this marathon revision task, I reverted PDF/EPUB versions to be a paid option again. Since then, I've managed to write three more books. I did Perl and Ruby one-liners (as part of the ongoing conversion of the CLI text processing repo) despite knowing sales won't be good enough to keep up the momentum. Then I wrote a <a href="https://learnbyexample.github.io/100_page_python_intro/">Python intro</a> book for those already familiar with programming basics. Published last month, sales are much lesser than I expected. Given Python is now 30 years old and there's no shortage of Python books for beginners, I shouldn't be surprised though. I'm probably grumpy because it took a month more than expected even though I already had decent material from my workshops. Anyway, my main motivation was to improve my Python knowledge and it did serve that purpose. As a bonus, I just got started with workshops again, conducted online (a first for me). The book is already proving useful as a handy reference for me as well as the students.</p>
<br>
<h2 id="feedback-and-criticism">Feedback and Criticism<a class="zola-anchor" href="#feedback-and-criticism" aria-label="Anchor link for: feedback-and-criticism">🔗</a></h2>
<p>Here's some of the feedback I've received over the past two years.</p>
<ul>
<li>Grammatical mistakes. Missing <code>a</code>, <code>an</code> and <code>the</code> articles were particularly jarring for the readers. If you couldn't tell from reading <em>this</em> article (heh) that English isn't my native language, I'll consider that I've improved a lot.</li>
<li>Some readers stated that they didn't bother checking out my books because the covers are so bad. I finally got a cover done by an artist for the <a href="https://learnbyexample.github.io/100_page_python_intro/">Python intro book</a>.</li>
<li>For the regexp books, a few readers said my introductions were light on content. So during the marathon book updates I did last year, I managed to add more details. I feel there's still plenty of room for improvement.</li>
<li>My comprehension is kinda average and it works better whenever I manage to create code snippets to prove or disprove my understanding. So, my books are heavily example oriented. I've received feedback that there are too many examples, explanations aren't sufficient, etc. I'm trying to improve on this count, but I doubt I can change my natural writing style.</li>
<li>A few readers wanted more exercises, which I was happy to oblige. It took me a while to accept that I should provide solutions as well.</li>
</ul>
<p>I did get a few negative feedback (ones I consider weren't constructive in nature). One such feedback affected me a lot, despite the encouraging sales for the second book. Over time, I've adapted but I'm still afraid of seeing one whenever I promote my books.</p>
<br>
<h2 id="self-publishing-experience">Self publishing experience<a class="zola-anchor" href="#self-publishing-experience" aria-label="Anchor link for: self-publishing-experience">🔗</a></h2>
<p>I don't have a personal experience with traditional publishing (other than the two review opportunities). After the initial success of 'Python re(gex)?' book, I was happy to stay being self published. When there was a dip, I did consider it would be nice to have the backing of a traditional publisher and a chance to improve the contents of my books.</p>
<p>What I like about self published:</p>
<ul>
<li>I can give away free copies whenever I want, change pricing, share the source code, put up free web version of the books, etc.
<ul>
<li>I'm aware of a few publishers allowing authors to put up free web copies, but it isn't universal.</li>
</ul>
</li>
<li>I can push updates easily and inform the readers as well.</li>
<li>No deadlines, other than self imposed ones. This is both good and bad. The good thing is that I can take my time. The bad thing is that the reduced pressure leads to longer schedule. I spend a lot of time on social media, reading fiction, watching entertainment, etc. The lockdown marathon did improve my average working hours, but there's still a lot of room for improvement.</li>
<li>I am not restricted by guidelines set by a publisher regarding chapter structure, images, exercises, etc.</li>
</ul>
<p>What I feel would improve with traditional publishing:</p>
<ul>
<li>Cover image</li>
<li>PDF/EPUB quality</li>
<li>Content quality, especially grammar</li>
<li>Audience reach</li>
</ul>
<p>Not sure how my earnings would be affected. On the one hand, I get minimum 80% on book sales. On the other hand, I'd probably reach a wider audience with traditional publishing. I did receive a few offers when my promotional posts were trending. One of the offer (for 'Python re(gex)?' ebook) had a joining bonus and initial advance — both combined was less than what I had already earned. But if they had extended the offer for other books as well, it would've been a much more tempting deal.</p>
<p>Currently, I'm happy with status quo. Always free web versions and free PDF/EPUB promotional sales kinda solves my donation problem before I started selling ebooks — I get paid and readers have a way to get the materials for free. I'm also inspired by FOSS products I use and authors like Al Sweigart and Allen B. Downey who give away quality learning resources for free.</p>
<p>That said, I wish I could improve my marketing skills. Or, somehow someone likes my books so much that their review attracts significant attention and my sales increase as a result. I've also considered trying out affiliates, but haven't even created a list of people to contact yet. I don't have analytics set up on Leanpub, my blog, web versions of my books, etc. Based on analytics that is available by default on GitHub and Gumroad, I do see a few links from schools and universities. I wish they would contact me, so that I can help if needed and improve my book contents based on their experiences.</p>
<br>
<h2 id="leanpub-vs-gumroad">Leanpub vs Gumroad<a class="zola-anchor" href="#leanpub-vs-gumroad" aria-label="Anchor link for: leanpub-vs-gumroad">🔗</a></h2>
<p>I started with Leanpub since I had seen a few posts from self published authors using this platform. By the time I had published the second book I got to know about Gumroad and was attracted by the pricing/payout structure. From then on, I have published on both platforms.</p>
<p>Here's what I like most about both these platforms:</p>
<ul>
<li>I can change pricing (including free option) and book contents any number of times</li>
<li>I can allow users to pay <em>more</em> than the product price, which is how I get paid during <em>free</em> promotional sales</li>
<li>I can inform readers whenever I update my books</li>
<li>I can create bundles</li>
<li>They handle collection of VAT (and other such fees)</li>
<li>Their payout options work for me in India</li>
</ul>
<p>Here's some differences and <em>my</em> opinions on some of their features:</p>
<ul>
<li>Gumroad's pricing structure is better. If you have a following like <a href="https://twitter.com/b0rk">Julia Evans</a>, pricing would make a huge difference</li>
<li>Gumroad gives analytics for free</li>
<li>Gumroad's email notification is opt-out compared to opt-in for Leanpub. Opt-in is better for readers, but in my experience less than 10% sign up and thus miss out when I want to send them book updates</li>
<li>Leanpub payout delay is 45-75 days, Gumroad is 7-14 days (or instant in some cases)</li>
<li>Leanpub's bundle feature is better since it doesn't require a new cover and files are automatically picked based on the links provided. In Gumroad, it is essentially a new product, but it does allow to manually pick files from existing ones. Also, Leanpub allows bundling with another author (which I have used and given me decent sales)</li>
<li>Leanpub's product page and UI is better. The sliding scale (along with information on author's share) to pick a price is clearer than Gumroad's manual price entry. And I don't like that Gumroad places the minimum price information away from the box where the user enters a price. On Leanpub, all of these are shown together and reduces confusion</li>
<li>Leanpub's product page has always ranked higher in search results in my experience</li>
<li>Leanpub's 45-day Guarantee and Sample chapters as part of the product page makes it easier for readers to take a risk</li>
<li>Leanpub has <a href="https://leanpub.com/newsletters">weekly/monthly sale newsletters</a> in which you could get featured. This has brought me significant earnings in the past few months. If you enable an option, Gumroad would promote your product too (for 10% extra fee) but this has given me very few sales compared to Leanpub's newsletter</li>
</ul>
<p align="center"><img src="/images/books/leanpub_price_ui.png" alt="Pricing UI on Leanpub" /></p>
<p align="center"><i>Pricing UI on Leanpub</i></p>
<p align="center"><img src="/images/books/gumroad_price_ui.png" alt="Pricing UI on Gumroad" /></p>
<p align="center"><i>Pricing UI on Gumroad</i></p>
<br>
<h2 id="pandoc-and-mdbook">pandoc and mdbook<a class="zola-anchor" href="#pandoc-and-mdbook" aria-label="Anchor link for: pandoc-and-mdbook">🔗</a></h2>
<p>I picked <a href="https://github.com/jgm/pandoc/">pandoc</a> to generate PDF from GitHub style markdown, as it seemed the most popular tool for this purpose. The default output is good enough, but I wanted to customize a lot of things. With help from documentation and various Stackoverflow/Stackexchange threads, I was able to generate an output to my liking. I didn't know about templates though, otherwise I could have researched about them and re-used solutions from others. I wrote a blog post about my learnings, visit <a href="https://learnbyexample.github.io/customizing-pandoc/">Customizing pandoc to generate beautiful pdf and epub from markdown</a> if you are interested.</p>
<p>Some readers wanted EPUB versions too. I thought it made sense for reading from mobile, but my own experience with this format on desktop was quite disappointing. Only later did I learn that I wasn't using a proper EPUB reader for technical books. Which is why I didn't realize that the default output from <code>pandoc</code> for EPUB was also good enough. During the revision marathon, I finally created EPUB versions too. I'd say I am still a beginner, but I did learn enough CSS and LaTeX to customize EPUB and PDF generation with <code>pandoc</code>.</p>
<p><code>pandoc</code> has its own enhanced version of markdown, which has a lot of nifty features for ebooks. But I chose to stick with GitHub style markdown. And it came in handy when I wanted to re-use book material for blog posts, generating web versions of the book with <a href="https://github.com/rust-lang/mdBook">mdbook</a> and so on. After I had decided to open source my books, I also wanted to make a web version that feels like a book instead of just the single page markdown source from the GitHub repos. I would've probably used Gitbook if they hadn't moved away from the legacy version. I came across <code>mdbook</code> as an alternate for Gitbook and I'm glad I did.</p>
<br>
<h2 id="future-plans">Future plans<a class="zola-anchor" href="#future-plans" aria-label="Anchor link for: future-plans">🔗</a></h2>
<p>I have certainly improved a lot as a writer since I first published my book in late 2018. But after 9 books, I'm finding it a lot more difficult to motivate myself to keep writing. See also <a href="https://news.ycombinator.com/item?id=20212090">HN discussion: Writing a book, still the same pain 15 years later</a> for another example.</p>
<p>I have plans to publish at least one more book in 2021 and revise my existing books (not comprehensive, but a few items have cropped up). I hope the current momentum can extend enough to cover my expenses for this year at least. Beyond that, I think I will write more books, but I'll have to mix it up with other things (such as video courses, interactive courses, freelancing, etc) to keep myself motivated. I just hope that this time I will be able to pick an alternative quickly.</p>
<br>
<h2 id="resources">Resources<a class="zola-anchor" href="#resources" aria-label="Anchor link for: resources">🔗</a></h2>
<p>I've been asked a few times regarding my experiences as an author (especially self publishing) and resources I've used. That was my primary intention in writing this blog post. I thought I'd add a bit of background as well, not the multi-section essay I ended up with. Anyway, here's some links that I've bookmarked related to book writing.</p>
<p><strong>Authors sharing their experiences</strong></p>
<ul>
<li><a href="https://www.jeffgeerling.com/blog/2020/self-publishing-and-2nd-edition-ansible-devops">Jeff Geerling's self-publishing experience</a></li>
<li><a href="https://jvns.ca/#on-writing-comics---zines">Julia Evans's articles on writing comics/zines</a></li>
<li><a href="https://jvns.ca/blog/2020/10/28/a-few-things-i-ve-learned-about-email-marketing/">Julia Evans's email marketing experience</a></li>
<li><a href="https://www.swyx.io/marketing-yourself/">Shawn Wang's article: How to Market Yourself</a></li>
<li><a href="https://andregarzia.com/2021/04/writing-a-technical-book.html">Andre Alves Garzia's article: Writing a Technical Book</a></li>
<li><a href="https://news.ycombinator.com/item?id=23818859">HN discussion: Writing a software book and making over $100k</a></li>
</ul>
<p><strong>Writing skills</strong></p>
<ul>
<li><a href="https://news.ycombinator.com/item?id=20070558">HN discussion: Tips for Writing a Technical Book</a></li>
<li><a href="https://news.ycombinator.com/item?id=22283919">HN discussion: Learning technical writing using the engineering method</a></li>
<li><a href="https://news.ycombinator.com/item?id=23281568">HN discussion: How to write a programming book</a></li>
<li><a href="https://github.com/jenniferlynparsons/awesome-writing">awesome-writing: list of information to help developers write better, kinder, more helpful documentation and learning materials</a></li>
<li><a href="https://github.com/joshuacc/prose-for-programmers">A book to help software developers write better prose (WIP)</a></li>
</ul>
<p><strong>Tools and Miscellaneous</strong></p>
<ul>
<li><a href="https://learnbyexample.github.io/customizing-pandoc/">Customizing pandoc to generate beautiful pdf and epub from markdown</a> — my own blog post, includes resource links for similar articles and tools other than <code>pandoc</code></li>
<li><a href="https://github.com/LisaDziuba/Awesome-Design-Tools">List of awesome design tools</a></li>
<li><a href="https://github.com/sw-yx/launch-cheatsheet/">launch-cheatsheet</a></li>
</ul>
<br>
<h2 id="a-parting-advice">A parting advice<a class="zola-anchor" href="#a-parting-advice" aria-label="Anchor link for: a-parting-advice">🔗</a></h2>
<p>Don't quit easily!</p>
<br>
Multiline fixed string search and replace with CLI tools2020-11-27T00:00:00+00:002025-02-10T00:00:00+00:00https://learnbyexample.github.io/multiline-search-and-replace/<p>This post shows how you can use the <code>ripgrep</code>, <code>perl</code> and <code>sd</code> commands to perform multiline fixed string search and replace operations from the command line. Solutions with <code>GNU sed</code> is also discussed, along with its limitations.</p>
<span id="continue-reading"></span><br>
<h2 id="fixed-string-matching">Fixed string matching<a class="zola-anchor" href="#fixed-string-matching" aria-label="Anchor link for: fixed-string-matching">🔗</a></h2>
<p>The below sample input file will be used in the examples in this post.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ cat ip.txt
</span><span>This is a multiline
</span><span>sample input with lots
</span><span>of special characters
</span><span>like . () * [] $ {}
</span><span>| ^ + ? \ and ' and so on.
</span><span>This post shows how
</span><span>you can do fixed
</span><span>-string multiline
</span><span>search with cli tools.
</span></code></pre>
<h3 id="ripgrep">ripgrep<a class="zola-anchor" href="#ripgrep" aria-label="Anchor link for: ripgrep">🔗</a></h3>
<p><a href="https://github.com/BurntSushi/ripgrep">ripgrep</a> supports the <code>-U</code> option to allow multiline matching. The <code>-F</code> option turns off regexp matching, i.e. the search string is treated literally. In the <code>bash</code> shell (and likely most other shells), you can press enter key to insert literal newline character for quoted values. When you do so, the next line starts with the secondary prompt <code>PS2</code>, which is usually <code>></code> and a space character. This isn't shown in the examples below to make it easier to copy-paste the commands.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ rg -UF 'like . () * [] $ {}
</span><span>| ^ + ? \ and' ip.txt
</span><span>4:like . () * [] $ {}
</span><span>5:| ^ + ? \ and ' and so on.
</span><span>
</span><span># use the -l option to get only the filename instead of matching lines
</span><span>$ rg -lUF 'like . () * [] $ {}
</span><span>| ^ + ? \ and' ip.txt
</span><span>ip.txt
</span></code></pre>
<p>You'll have an issue if your search string itself contains single quote characters. Avoid using double quotes as a workaround, as that has its own set of special characters. You can work around by concatenating multiple strings next to each other, along with escaped single quote characters as needed.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span># the -N option disables line number prefix
</span><span>$ rg -NUF 'like . () * [] $ {}
</span><span>| ^ + ? \ and '\'' and' ip.txt
</span><span>like . () * [] $ {}
</span><span>| ^ + ? \ and ' and so on.
</span></code></pre>
<p>If your search string starts with the <code>-</code> character, you'll have to use <code>--</code> before the search argument.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ rg -NUF -- '-string multiline
</span><span>search' ip.txt
</span><span>-string multiline
</span><span>search with cli tools.
</span></code></pre>
<h3 id="perl">perl<a class="zola-anchor" href="#perl" aria-label="Anchor link for: perl">🔗</a></h3>
<p>You can use the <code>-0777</code> option with <code>perl</code> to slurp the entire input as a single string. Another advantage with <code>perl</code> is that you can use files to pass the search and replace strings. Thus, you don't have to worry about any character that may clash with shell metacharacters. See my <a href="https://learnbyexample.github.io/learn_perl_oneliners/">Perl One-Liners Guide</a> if you are not familiar with using <code>perl</code> from the command line.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ cat search_1.txt
</span><span>like . () * [] $ {}
</span><span>| ^ + ? \ and ' and so on.
</span><span>
</span><span># display filename if the given search string matches
</span><span>$ perl -0777 -nE '!$#ARGV ? $s=$_ :
</span><span> /\Q$s/ && say $ARGV' search_1.txt ip.txt
</span><span>ip.txt
</span></code></pre>
<p>However, you'll have to make sure the file doesn't end with a newline if you are providing partial lines for searching, or take care of it within the <code>perl</code> script.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ cat search_2.txt
</span><span>-string multiline
</span><span>search
</span><span>
</span><span># no output because there's a newline at the end of search_2.txt file
</span><span>$ perl -0777 -nE '!$#ARGV ? $s=$_ :
</span><span> /\Q$s/ && say $ARGV' search_2.txt ip.txt
</span><span>
</span><span># this will remove newline from the end of file before assigning to $s
</span><span>$ perl -0777 -nE '!$#ARGV ? $s=s/\n\z//r :
</span><span> /\Q$s/ && say $ARGV' search_2.txt ip.txt
</span><span>ip.txt
</span></code></pre>
<p>By default, <code>ripgrep</code> gives entire matching lines. To get rest of the line with <code>perl</code>, you'll have to explicitly add a pattern around the search string.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span># $& variable has the entire matching portion
</span><span>$ perl -0777 -nE '!$#ARGV ? $s=s/\n\z//r :
</span><span> /\Q$s/ && say $&' search_2.txt ip.txt
</span><span>-string multiline
</span><span>search
</span><span>
</span><span># use 'say $& while /.*\Q$s\E.*/g' if there are multiple matches
</span><span>$ perl -0777 -nE '!$#ARGV ? $s=s/\n\z//r :
</span><span> /.*\Q$s\E.*/ && say $&' search_2.txt ip.txt
</span><span>-string multiline
</span><span>search with cli tools.
</span></code></pre>
<br>
<h2 id="fixed-string-substitution">Fixed string substitution<a class="zola-anchor" href="#fixed-string-substitution" aria-label="Anchor link for: fixed-string-substitution">🔗</a></h2>
<h3 id="ripgrep-1">ripgrep<a class="zola-anchor" href="#ripgrep-1" aria-label="Anchor link for: ripgrep-1">🔗</a></h3>
<p><code>ripgrep</code> also supports replacing the matched string with something else using the <code>-r</code> option. By default, you'll see only matched lines in the output. Use the <code>--passthru</code> option to display all the input lines, even if they do not match the given search string. See <a href="https://learnbyexample.github.io/substitution-with-ripgrep/">my blog post</a> for more details about the <code>-r</code> option and various ways you can use it for substitution requirements.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ rg --passthru -NUF 'like . () * [] $ {}
</span><span>| ^ + ? \ and' -r '====
</span><span>----
</span><span>====' ip.txt
</span><span>This is a multiline
</span><span>sample input with lots
</span><span>of special characters
</span><span>====
</span><span>----
</span><span>==== ' and so on.
</span><span>This post shows how
</span><span>you can do fixed
</span><span>-string multiline
</span><span>search with cli tools.
</span></code></pre>
<p>Apart from having to workaround single quote, you'll have to use <code>$$</code> instead of <code>$</code> as it is used for backreferences in the replacement section.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ echo 'sample input' | rg --passthru -F 'in' -r '$a'
</span><span>sample put
</span><span>$ echo 'sample input' | rg --passthru -F 'in' -r '$$a'
</span><span>sample $aput
</span></code></pre>
<h3 id="perl-1">perl<a class="zola-anchor" href="#perl-1" aria-label="Anchor link for: perl-1">🔗</a></h3>
<p>With <code>perl</code>, you can use files for both the search and replace strings. And, you can easily choose to replace the first or all occurrences, unlike <code>ripgrep</code> where it always replaces all the matches.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ cat replace.txt
</span><span>---------------------
</span><span>$& = $1 + $2 / 3 \ 4
</span><span>=====================
</span><span>
</span><span>$ perl -0777 -ne '$#ARGV==1 ? $s=$_ : $#ARGV==0 ? $r=$_ :
</span><span> print s/\Q$s/$r/gr' search_1.txt replace.txt ip.txt
</span><span>This is a multiline
</span><span>sample input with lots
</span><span>of special characters
</span><span>---------------------
</span><span>$& = $1 + $2 / 3 \ 4
</span><span>=====================
</span><span>This post shows how
</span><span>you can do fixed
</span><span>-string multiline
</span><span>search with cli tools.
</span></code></pre>
<p>As seen before, you'll have to remove newline from the search string for partial line matching.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span># use $r=s/\n\z//r to avoid trailing newline from replace.txt
</span><span>$ perl -0777 -ne '$#ARGV==1 ? $s=s/\n\z//r : $#ARGV==0 ? $r=$_ :
</span><span> print s/\Q$s/$r/gr' search_2.txt replace.txt ip.txt
</span><span>This is a multiline
</span><span>sample input with lots
</span><span>of special characters
</span><span>like . () * [] $ {}
</span><span>| ^ + ? \ and ' and so on.
</span><span>This post shows how
</span><span>you can do fixed
</span><span>---------------------
</span><span>$& = $1 + $2 / 3 \ 4
</span><span>=====================
</span><span> with cli tools.
</span></code></pre>
<h3 id="sd">sd<a class="zola-anchor" href="#sd" aria-label="Anchor link for: sd">🔗</a></h3>
<p><a href="https://github.com/chmln/sd">sd</a> supports a fixed string option and Rust regexp based substitution. Unlike <code>ripgrep</code>, the <code>-s</code> option for fixed string will apply to both the search and replacement sections. <code>sd</code> does in-place editing for file inputs by default, you can use <code>-p</code> to preview results on the terminal. Multiline matching is automatically performed by default.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ echo 'sample input' | sd -s 'in' '$a'
</span><span>sample $aput
</span><span>
</span><span>$ sd -ps 'like . () * [] $ {}
</span><span>| ^ + ? \ and' '====
</span><span>----
</span><span>====' ip.txt
</span><span>This is a multiline
</span><span>sample input with lots
</span><span>of special characters
</span><span>====
</span><span>----
</span><span>==== ' and so on.
</span><span>This post shows how
</span><span>you can do fixed
</span><span>-string multiline
</span><span>search with cli tools.
</span></code></pre>
<br>
<h2 id="saving-file-contents-to-a-variable">Saving file contents to a variable<a class="zola-anchor" href="#saving-file-contents-to-a-variable" aria-label="Anchor link for: saving-file-contents-to-a-variable">🔗</a></h2>
<p>Trailing newlines and ASCII NUL characters will be lost if you wish to save contents of a file as <code>bash</code> variables using the <code>var=$(< filename)</code> command. See <a href="https://stackoverflow.com/a/22607352/4082052">stackoverflow: pitfalls of reading file into shell variable</a> for more details.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ printf '\na\0b\n123\n\n\n\n\n\n\n\n' > t1
</span><span>$ a=$(< t1)
</span><span>bash: warning: command substitution: ignored null byte in input
</span><span>
</span><span># NUL character is lost after the assignment
</span><span># all the trailing newlines are lost as well
</span><span>$ printf '%b' "$a" | cat -A
</span><span>$
</span><span>ab$
</span><span>123
</span></code></pre>
<h3 id="ripgrep-2">ripgrep<a class="zola-anchor" href="#ripgrep-2" aria-label="Anchor link for: ripgrep-2">🔗</a></h3>
<p>If your search string doesn't have multiple trailing newlines or ASCII NUL characters, then you can save file contents to variables and then pass them to <code>ripgrep</code>. Single trailing newline will not normally cause an issue for searching operations as <code>ripgrep</code> will append a newline while displaying results anyway. If you want to make sure input file also contains the trailing newline, then you can manually concatenate a newline character to the search string.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ s=$(< search_1.txt)
</span><span># use "$s"$'\n' if you want to match trailing newline as well
</span><span>$ rg -NUF "$s" ip.txt
</span><span>like . () * [] $ {}
</span><span>| ^ + ? \ and ' and so on.
</span><span>
</span><span># use -- if the search string starts with a - character
</span><span>$ s=$(< search_2.txt)
</span><span>$ rg -NUF -- "$s" ip.txt
</span><span>-string multiline
</span><span>search with cli tools.
</span></code></pre>
<p>For substitution operations, you'll have to preprocess the replacement file to replace <code>$</code> with <code>$$</code>.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ s=$(< search_1.txt)
</span><span>$ r=$(sed 's/\$/$$/g' replace.txt)
</span><span>
</span><span># here, removal of trailing newline doesn't cause an issue,
</span><span># as it evens out between the search and replace strings
</span><span>$ rg --passthru -NUF "$s" -r "$r" ip.txt
</span><span>This is a multiline
</span><span>sample input with lots
</span><span>of special characters
</span><span>---------------------
</span><span>$& = $1 + $2 / 3 \ 4
</span><span>=====================
</span><span>This post shows how
</span><span>you can do fixed
</span><span>-string multiline
</span><span>search with cli tools.
</span></code></pre>
<p>Here, partial line has to be matched. So, <code>$()</code> assignment works well for the search string. If the trailing newline of the replacement string isn't needed, then <code>$()</code> assignment again is good enough. Otherwise, you can modify the replacement string as <code>-r "$r"$'\n'</code></p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span>$ s=$(< search_2.txt)
</span><span>$ r=$(sed 's/\$/$$/g' replace.txt)
</span><span>
</span><span>$ rg --passthru -NUF -r "$r" -- "$s" ip.txt
</span><span>This is a multiline
</span><span>sample input with lots
</span><span>of special characters
</span><span>like . () * [] $ {}
</span><span>| ^ + ? \ and ' and so on.
</span><span>This post shows how
</span><span>you can do fixed
</span><span>---------------------
</span><span>$& = $1 + $2 / 3 \ 4
</span><span>===================== with cli tools.
</span></code></pre>
<h3 id="sd-1">sd<a class="zola-anchor" href="#sd-1" aria-label="Anchor link for: sd-1">🔗</a></h3>
<p>As mentioned before, the <code>-s</code> option for <code>sd</code> applies to both the search and replacement sections. So, the usage is lot simpler compared to <code>ripgrep</code>.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span># -- is needed here because replace.txt starts with a - character
</span><span>$ sd -ps -- "$(< search_1.txt)" "$(< replace.txt)" ip.txt
</span><span>This is a multiline
</span><span>sample input with lots
</span><span>of special characters
</span><span>---------------------
</span><span>$& = $1 + $2 / 3 \ 4
</span><span>=====================
</span><span>This post shows how
</span><span>you can do fixed
</span><span>-string multiline
</span><span>search with cli tools.
</span></code></pre>
<h3 id="gnu-sed">GNU sed<a class="zola-anchor" href="#gnu-sed" aria-label="Anchor link for: gnu-sed">🔗</a></h3>
<p>To follow a similar approach with <code>GNU sed</code>, you'll have to preprocess the strings to escape metacharacters. Assuming input doesn't have ASCII NUL characters, you can use <code>-z</code> option to slurp the entire input as a single string.</p>
<p>Here's an example for multiline search.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span># escape all BRE metacharacters
</span><span># replace literal newlines with \n
</span><span>$ s=$(sed -z 's#[[^$*.\/]#\\&#g; s/\n/\\n/g' search_1.txt)
</span><span>
</span><span># since newlines are replaced with \n,
</span><span># trailing newlines will be preserved here
</span><span>$ echo "$s"
</span><span>like \. () \* \[] \$ {}\n| \^ + ? \\ and ' and so on\.\n
</span><span>
</span><span># display filename if input matches the given multiline search string
</span><span># tr is used to change the NUL character after filename to newline
</span><span>$ sed -nz '/'"$s"'/F' ip.txt | tr '\0' '\n'
</span><span>ip.txt
</span></code></pre>
<p>And here's an example for multiline substitution.</p>
<pre style="background-color:#f5f5f5;color:#1f1f1f;"><code><span># last newline is removed here to allow partial line matching
</span><span>$ s=$(sed -z 's#[[^$*.\/]#\\&#g; s/\n$//; s/\n/\\n/g' search_2.txt)
</span><span>
</span><span># escape all replacement section metacharacters
</span><span># and prefix \ character to literal newlines, except the last line
</span><span>$ r=$(sed 's:[\\/&]:\\&:g; $!s/$/\\/' replace.txt)
</span><span>$ echo "$r"
</span><span>---------------------\
</span><span>$\& = $1 + $2 \/ 3 \\ 4\
</span><span>=====================
</span><span>
</span><span># if you need the trailing newline from replace.txt,
</span><span># use sed -z 's/'"$s"'/'"$r"'\n/g'
</span><span>$ sed -z 's/'"$s"'/'"$r"'/g' ip.txt
</span><span>This is a multiline
</span><span>sample input with lots
</span><span>of special characters
</span><span>like . () * [] $ {}
</span><span>| ^ + ? \ and ' and so on.
</span><span>This post shows how
</span><span>you can do fixed
</span><span>---------------------
</span><span>$& = $1 + $2 / 3 \ 4
</span><span>===================== with cli tools.
</span></code></pre>
<br>
<h2 id="linux-cli-ebooks">Linux CLI ebooks<a class="zola-anchor" href="#linux-cli-ebooks" aria-label="Anchor link for: linux-cli-ebooks">🔗</a></h2>
<p>Check out <a href="https://learnbyexample.github.io/books/">my ebooks</a> if you are interested in learning more about Linux CLI basics, coreutils, text processing tools like <code>GNU grep</code>, <code>GNU sed</code>, <code>GNU awk</code> and <code>perl</code>.</p>
Emulating regexp lookarounds in GNU sed2020-10-31T00:00:00+00:002021-03-19T00:00:00+00:00https://learnbyexample.github.io/sed-lookarounds/<p>This <a href="https://stackoverflow.com/q/64371281/4082052">stackoverflow Q&A</a> got me thinking about various ways to construct a solution in <code>GNU sed</code> if lookarounds are needed.</p>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> Only single line (with newline as the line separator) processing is presented here. Equivalent lookaround syntax with <code>grep -P</code> or <code>perl</code> is also shown for comparison. Cases where multiple lines and/or ASCII NUL characters are present in the pattern space is left as an exercise.</p>
</blockquote>
<span id="continue-reading"></span><br>
<h2 id="filtering">Filtering<a class="zola-anchor" href="#filtering" aria-label="Anchor link for: filtering">🔗</a></h2>
<p>Here, you only need to decide whether the input line has to be matched or not. <code>sed</code> supports grouping commands inside <code>{}</code> that should be executed only if a filtering condition is matched. The condition could be negated by adding a <code>!</code> character. In this way, you can emulate chaining of multiple positive and/or negative lookaround conditions.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat items.txt
</span><span style="color:#b3933a;">1</span><span>,</span><span style="color:#b3933a;">2</span><span>,</span><span style="color:#b3933a;">3</span><span>,</span><span style="color:#b3933a;">4
</span><span>apple=</span><span style="color:#b3933a;">50 </span><span>;per kg
</span><span>a,b,c,d
</span><span>;foo xyz3
</span><span>
</span><span style="color:#7f8989;"># lines containing a digit character followed by a ; character anywhere after
</span><span style="color:#7f8989;"># lookaround isn't needed here
</span><span style="color:#7f8989;"># same as: grep '[0-9].*;' or grep -P '\d(?=.*;)'
</span><span>$ sed </span><span style="color:#72ab00;">-</span><span>n </span><span style="color:#d07711;">'/[0-9].*;/p'</span><span> items.txt
</span><span>apple=</span><span style="color:#b3933a;">50 </span><span>;per kg
</span><span>
</span><span style="color:#7f8989;"># lines containing both digit and ; characters in any order
</span><span style="color:#7f8989;"># same as: grep -P '^(?=.*;).*\d'
</span><span>$ sed </span><span style="color:#72ab00;">-</span><span>n </span><span style="color:#d07711;">'/;/{ /[0-9]/p }'</span><span> items.txt
</span><span>apple=</span><span style="color:#b3933a;">50 </span><span>;per kg
</span><span>;foo xyz3
</span><span>
</span><span style="color:#7f8989;"># lines containing both digit and ; characters
</span><span style="color:#7f8989;"># but not if the line also contains character a
</span><span style="color:#7f8989;"># same as: grep -P '^(?!.*a)(?=.*;).*\d'
</span><span>$ sed </span><span style="color:#72ab00;">-</span><span>n </span><span style="color:#d07711;">'/a/!{ /;/{ /[0-9]/p } }'</span><span> items.txt
</span><span>;foo xyz3
</span></code></pre>
<p>For some cases, multiple condition check like the previous examples is not enough. For example, filter a line if it contains <code>par</code> as long as <code>cart</code> isn't present later in the line. Presence of <code>cart</code> earlier in the line shouldn't affect the outcome. In such cases, you can first change the input line to add a newline character wherever <code>cart</code> is present and then construct a condition such that it depends on the newline character instead of <code>cart</code>. If a match is found, delete all the newline characters and then print the line.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'par carted spare cart park city\na parking cart\n'
</span><span>
</span><span style="color:#7f8989;"># same as: grep -P 'par(?!.*cart)'
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%b</span><span style="color:#d07711;">' "$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span>n </span><span style="color:#d07711;">'s/cart/\n&/g; /par[^\n]*$/{ s/\n//g; p }'
</span><span>par carted spare cart park city
</span></code></pre>
<blockquote>
<p><img src="/images/info.svg" alt="info" /> Newline is a safe character to choose for default line by line processing, as <code>sed</code> removes it from the pattern space. If you are processing a pattern space that contains newline character (for example: <code>-z</code> option, <code>N</code> command, etc), then you can still perform this trick as long as you know a character that is guaranteed to be absent from the input data. </p>
</blockquote>
<h2 id="substitution">Substitution<a class="zola-anchor" href="#substitution" aria-label="Anchor link for: substitution">🔗</a></h2>
<p>In the previous section, you saw how to modify input line with newline character to make it easier to construct a lookaround condition. This trick comes in handy for substitution as well. However, for search and replace cases, you also need to emulate zero-width nature of lookarounds. To achieve this, you can make use of <code>t</code> command to construct a loop that performs substitution as long as a match is found. See my chapter on <a href="https://learnbyexample.github.io/learn_gnused/control-structures.html">Control structures</a> for more details about branching commands in <code>GNU sed</code>.</p>
<p>Here's an example of looping. Aim is to delete <code>fin</code> from the given input recursively.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># manual repetition, assuming count is known
</span><span>$ echo </span><span style="color:#d07711;">'coffining' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/fin//'
</span><span>cofing
</span><span>$ echo </span><span style="color:#d07711;">'coffining' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/fin//; s///'
</span><span>cog
</span><span>
</span><span style="color:#7f8989;"># :loop marks the 's' command with label 'loop'
</span><span style="color:#7f8989;"># tloop will jump to label 'loop' as long as the substitution succeeds
</span><span>$ echo </span><span style="color:#d07711;">'coffining' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">':loop s/fin//; tloop'
</span><span>cog
</span></code></pre>
<h3 id="negative-lookarounds">Negative lookarounds<a class="zola-anchor" href="#negative-lookarounds" aria-label="Anchor link for: negative-lookarounds">🔗</a></h3>
<p>Some cases can be solved by performing substitution only if a condition is first satisfied. For this example, need to first select lines if it doesn't start with a <code>;</code> character. Then, for such lines, remove everything from the first space or comma character. Note that <code>{}</code> grouping is optional here.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># same as: perl -ne 'print if s/^(?!;).*?\K[ ,].*//'
</span><span>$ sed </span><span style="color:#72ab00;">-</span><span>n </span><span style="color:#d07711;">'/^;/! s/[ ,].*//p'</span><span> items.txt
</span><span style="color:#b3933a;">1
</span><span>apple=</span><span style="color:#b3933a;">50
</span><span>a
</span></code></pre>
<p>For this example, need to change <code>foo</code> to <code>[baz]</code> only if it is not followed by a digit character. Note that <code>foo</code> at the end of string also satisfies this assertion. <code>foofoo</code> has two matches as the assertion is zero-width in nature, i.e. it doesn't consume characters. Here, the first step is inserting a newline character between <code>foo</code> and a digit character. Then change all <code>foo</code> to <code>[baz]</code> as long as it is at the end of string or if it isn't followed by a newline character. Once the loop ends, remove all the newline characters.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'hey food! foo42 foot5 foofoo'
</span><span>
</span><span style="color:#7f8989;"># same as: perl -pe 's/foo(?!\d)/[baz]/g'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/(foo)([0-9])/\1\n\2/g;
</span><span style="color:#d07711;"> :a s/foo([^\n]|$)/[baz]\1/; ta;
</span><span style="color:#d07711;"> s/\n//g'
</span><span>hey [baz]d! foo42 [baz]t5 [baz][baz]
</span></code></pre>
<p>Change <code>foo</code> to <code>[baz]</code> only if it is not preceded by <code>_</code> character. <code>foo</code> at the start of string is matched as well.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'foo _foo 42foofoo'
</span><span>
</span><span style="color:#7f8989;"># same as: perl -pe 's/(?<!_)foo/[baz]/g'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/(_)(foo)/\1\n\2/g;
</span><span style="color:#d07711;"> :a s/(^|[^\n])foo/\1[baz]/; ta;
</span><span style="color:#d07711;"> s/\n//g'
</span><span>[baz] _foo </span><span style="color:#b3933a;">42</span><span>[baz][baz]
</span></code></pre>
<p>Replace <code>par</code> with <code>[xyz]</code> as long as <code>s</code> character is not present later in the input. This assumes that the assertion doesn't conflict with the search pattern, for example <code>s</code> will not conflict with <code>par</code> but would affect if it was <code>r</code> and <code>par</code>.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'par spare part party'
</span><span>
</span><span style="color:#7f8989;"># same as: perl -pe 's/par(?!.*s)/[xyz]/g'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/s/&\n/g;
</span><span style="color:#d07711;"> :a s/par([^\n]*)$/[xyz]\1/; ta;
</span><span style="color:#d07711;"> s/\n//g'
</span><span>par s[xyz]e [xyz]t [xyz]ty
</span></code></pre>
<p>Replace all empty fields with <code>NA</code> for csv input (assuming no embedded comma, newline characters, etc).</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">',1,,,two,3,,,'
</span><span>
</span><span style="color:#7f8989;"># same as: perl -lpe 's/(?<![^,])(?![^,])/NA/g'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">':a s/,,/,NA,/g; ta; s/^,/NA,/; s/,$/,NA/'
</span><span style="color:#5597d6;">NA</span><span>,</span><span style="color:#b3933a;">1</span><span>,</span><span style="color:#5597d6;">NA</span><span>,</span><span style="color:#5597d6;">NA</span><span>,two,</span><span style="color:#b3933a;">3</span><span>,</span><span style="color:#5597d6;">NA</span><span>,</span><span style="color:#5597d6;">NA</span><span>,</span><span style="color:#5597d6;">NA
</span></code></pre>
<p>Replace if <code>go</code> is not there between <code>at</code> and <code>par</code>.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'fox,cat,dog,parrot,dot,park,bat,go,spare,sat-in-a-park'
</span><span>
</span><span style="color:#7f8989;"># same as: perl -pe 's/at((?!go).)*par/[xyz]/g'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/go/\n&/g; s/at[^\n]*par/[xyz]/g; s/\n//g'
</span><span>fox,c[xyz]k,bat,go,spare,s[xyz]k
</span></code></pre>
<h3 id="positive-lookarounds">Positive lookarounds<a class="zola-anchor" href="#positive-lookarounds" aria-label="Anchor link for: positive-lookarounds">🔗</a></h3>
<p>In this example, need to surround fields with <code>[]</code> except first and last fields for csv input (assuming no embedded comma, newline characters, etc). With positive lookaround emulation, the modified string may continue to satisfy the matching condition, resulting in infinite looping. In this example, the fields themselves may contain <code>[]</code> characters, so you cannot use them to prevent infinite loop. The newline character trick comes in handy again.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'1,t[w]o,[3],f[ou]r,5'
</span><span>
</span><span style="color:#7f8989;"># same as: perl -pe 's/(?<=,)[^,]+(?=,)/[$&]/g'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">':a s/,([^,\n]+),/,\n[\1],/g; ta; s/\n//g'
</span><span style="color:#b3933a;">1</span><span>,[t[w]o],[[</span><span style="color:#b3933a;">3</span><span>]],[f[ou]r],</span><span style="color:#b3933a;">5
</span></code></pre>
<p>Add space at word boundaries, but not at the start or end of string. Also, don't add space if it is already present. Here, negated character class on space character is enough to emulate the assertion.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'total= num1+35*42/num2'
</span><span>
</span><span style="color:#7f8989;"># same as: perl -lpe 's/(?<=[^ ])\b(?=[^ ])/ /g'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">':a s/([^ ])\b([^ ])/\1 \2/; ta;'
</span><span>total </span><span style="color:#72ab00;">=</span><span> num1 </span><span style="color:#72ab00;">+ </span><span style="color:#b3933a;">35 </span><span style="color:#72ab00;">* </span><span style="color:#b3933a;">42 </span><span style="color:#72ab00;">/</span><span> num2
</span></code></pre>
<p>Replace <code>par</code> with <code>[xyz]</code> as long as <code>part</code> occurs as a whole word later in the line. Here, the nature of the modified string itself prevents the possibility of infinite loop.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'par spare part party'
</span><span>
</span><span style="color:#7f8989;"># same as: perl -pe 's/par(?=.*\bpart\b)/[xyz]/g'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">':a s/par(.*\bpart\b)/[xyz]\1/; ta'
</span><span>[xyz] s[xyz]e part party
</span></code></pre>
<h2 id="summary">Summary<a class="zola-anchor" href="#summary" aria-label="Anchor link for: summary">🔗</a></h2>
<p>Branching commands and some creative preprocessing of the input can be combined to emulate lookaround assertions in <code>sed</code>. Given that <a href="https://catonmat.net/proof-that-sed-is-turing-complete">Unix utility sed is Turing complete</a>, it's perhaps not a big surprise. Now, please excuse me, I'll be busy reaping points on stackoverflow/unix.stackexchange for this edge case ;)</p>
Search and replace tricks with ripgrep2020-09-16T00:00:00+00:002023-02-09T00:00:00+00:00https://learnbyexample.github.io/substitution-with-ripgrep/<p><a href="https://github.com/BurntSushi/ripgrep">ripgrep</a> (command name <code>rg</code>) is a <code>grep</code> tool, but supports search and replace as well. <code>rg</code> is far from a like-for-like alternate for <code>sed</code>, but it has nifty features like multiline replacement, fixed string matching, <code>PCRE2</code> support, etc. This post gives an overview of syntax for substitution and highlights some of the cases where <code>rg</code> is a handy replacement for <code>sed</code>.</p>
<span id="continue-reading"></span><br>
<h2 id="global-search-and-replace">Global search and replace<a class="zola-anchor" href="#global-search-and-replace" aria-label="Anchor link for: global-search-and-replace">🔗</a></h2>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ cat ip.txt
</span><span>dark blue, light blue
</span><span>light orange
</span><span>blue sky
</span><span>
</span><span style="color:#7f8989;"># by default, line number is displayed if output destination is stdout
</span><span style="color:#7f8989;"># by default, only lines that matched the given pattern is displayed
</span><span style="color:#7f8989;"># 'blue' is search pattern and -r 'red' is replacement string
</span><span>$ rg </span><span style="color:#d07711;">'blue' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'red'</span><span> ip.txt
</span><span style="color:#b3933a;">1:dark</span><span> red, light red
</span><span style="color:#b3933a;">3:red</span><span> sky
</span><span>
</span><span style="color:#7f8989;"># --passthru option is useful to print all lines, whether or not it matched
</span><span style="color:#7f8989;"># -N will disable line number prefix
</span><span style="color:#7f8989;"># this command is similar to: sed 's/blue/red/g' ip.txt
</span><span>$ rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">N </span><span style="color:#d07711;">'blue' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'red'</span><span> ip.txt
</span><span>dark red, light red
</span><span>light orange
</span><span>red sky
</span></code></pre>
<br>
<h2 id="matching-nth-occurrence">Matching Nth occurrence<a class="zola-anchor" href="#matching-nth-occurrence" aria-label="Anchor link for: matching-nth-occurrence">🔗</a></h2>
<p>As seen in previous example, <code>rg</code> will search and replace all occurrences. So, you'll have to be creative with regexp to replace only a specific occurrence per input line.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'see bat hot at but at go gate at sat at but at'
</span><span>
</span><span style="color:#7f8989;"># replace first occurrence only
</span><span style="color:#7f8989;"># same as: sed 's/\bat\b/[xyz]/'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">N </span><span style="color:#d07711;">'\bat\b(.*)' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'[xyz]$1'
</span><span>see bat hot [xyz] but at go gate at sat at but at
</span><span>
</span><span style="color:#7f8989;"># same as: sed 's/\bat\b/[xyz]/3'
</span><span style="color:#7f8989;"># the number within {} is N-1 to replace Nth occurrence, for N>1
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">N </span><span style="color:#d07711;">'^((.*?\bat\b){2}.*?)\bat\b' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'$1[xyz]'
</span><span>see bat hot at but at go gate [xyz] sat at but at
</span><span>
</span><span style="color:#7f8989;"># replace last but Nth occurrence, for N>=0
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">N </span><span style="color:#d07711;">'^(.*)\bat\b((.*\bat\b){3})' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'$1[xyz]$2'
</span><span>see bat hot at but [xyz] go gate at sat at but at
</span></code></pre>
<br>
<h2 id="in-place-workaround">In-place workaround<a class="zola-anchor" href="#in-place-workaround" aria-label="Anchor link for: in-place-workaround">🔗</a></h2>
<p><code>rg</code> doesn't support in-place option, so you'll have to do it yourself.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># -N isn't needed here as output destination is a file
</span><span style="color:#7f8989;"># same as: sed -i 's/blue/red/g' ip.txt
</span><span>$ rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#d07711;">'blue' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'red'</span><span> ip.txt </span><span style="color:#72ab00;">></span><span> tmp.txt </span><span style="color:#72ab00;">&&</span><span> mv tmp.txt ip.txt
</span><span>
</span><span>$ cat ip.txt
</span><span>dark red, light red
</span><span>light orange
</span><span>red sky
</span></code></pre>
<p>If you have <a href="https://joeyh.name/code/moreutils/">moreutils installed</a>, then you could use <code>sponge</code> as well.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#d07711;">'blue' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'red'</span><span> ip.txt </span><span style="color:#72ab00;">|</span><span> sponge ip.txt
</span></code></pre>
<br>
<h2 id="rust-regex-and-pcre2">Rust regex and PCRE2<a class="zola-anchor" href="#rust-regex-and-pcre2" aria-label="Anchor link for: rust-regex-and-pcre2">🔗</a></h2>
<p>By default, <code>rg</code> uses Rust regular expressions, which is much more featured compared to <code>GNU sed</code>. The main feature not supported is backreference within regexp definition (for performance reasons). See <a href="https://docs.rs/regex/1.3.9/regex/index.html">Rust regex documentation</a> for regular expression syntax and features. <code>rg</code> supports Unicode by default.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># non-greedy quantifier is supported
</span><span>$ s=</span><span style="color:#d07711;">'food land bark sand band cue combat'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#d07711;">'foo.*?ba' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'[xyz]'
</span><span>[xyz]rk sand band cue combat
</span><span>
</span><span style="color:#7f8989;"># unicode support
</span><span>$ echo </span><span style="color:#d07711;">'fox:αλεπού,eagle:αετός' </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#d07711;">'\p{L}+' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'($0)'
</span><span>(fox)</span><span style="color:#72ab00;">:</span><span>(αλεπού),(eagle)</span><span style="color:#72ab00;">:</span><span>(αετός)
</span><span>
</span><span style="color:#7f8989;"># set operator example, remove all punctuation characters except . ! and ?
</span><span>$ para=</span><span style="color:#d07711;">'"hi", there! how *are* you? all fine here.'
</span><span>$ echo </span><span style="color:#d07711;">"$para" </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#d07711;">'[[:punct:]--[.!?]]+' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">''
</span><span>hi there! how are you? all fine here.
</span></code></pre>
<p>The <code>-P</code> switch will enable <a href="https://www.pcre.org/current/doc/html/index.html">PCRE2</a> flavor, which has even more tricks. You can also use <code>--engine=auto</code> to allow <code>rg</code> to automatically use <code>PCRE2</code> when needed (for example: useful as an alias for <code>rg</code> command so that it gives performance of Rust engine by default and use <code>PCRE2</code> only when needed).</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># backreference within regexp definition
</span><span>$ s=</span><span style="color:#d07711;">'cocoa appleseed tool speechless'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#72ab00;">-</span><span>wP </span><span style="color:#d07711;">'([a-z]*([a-z])\2[a-z]*){2}' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'{$0}'
</span><span>cocoa {appleseed} tool {speechless}
</span><span>
</span><span style="color:#7f8989;"># replace all whole words except 'imp' and 'ant'
</span><span>$ s=</span><span style="color:#d07711;">'tiger imp goat eagle ant important'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">P </span><span style="color:#d07711;">'\b(imp|ant)\b(*SKIP)(*F)|\w+' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'[$0]'
</span><span>[tiger] imp [goat] [eagle] ant [important]
</span><span>
</span><span style="color:#7f8989;"># recursively match parentheses
</span><span>$ eqn=</span><span style="color:#d07711;">'(3+a)x * y((r-2)*(t+2)/6) + z(a(b(c(d(e)))))'
</span><span>$ echo </span><span style="color:#d07711;">"$eqn" </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">P </span><span style="color:#d07711;">'\((?:[^()]++|(?0))++\)' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">''
</span><span>x </span><span style="color:#72ab00;">*</span><span> y </span><span style="color:#72ab00;">+</span><span> z
</span><span>
</span><span>$ </span><span style="color:#7f8989;"># all lowercase letters and optional hyphen combo from start of string
</span><span>$ s=</span><span style="color:#d07711;">'apple-fig-mango guava grape'
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">P </span><span style="color:#d07711;">'\G([a-z]+)(-)?' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'($1)$2'
</span><span>(apple)</span><span style="color:#72ab00;">-</span><span>(fig)</span><span style="color:#72ab00;">-</span><span>(mango) guava grape
</span></code></pre>
<br>
<h2 id="extract-and-modify">Extract and modify<a class="zola-anchor" href="#extract-and-modify" aria-label="Anchor link for: extract-and-modify">🔗</a></h2>
<p>The <code>-r</code> option can be used when <code>-o</code> option is active too. The example shown below is not easy to do with <code>sed</code>.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'0501 035 154 12 26 98234'
</span><span>
</span><span style="color:#7f8989;"># numbers >= 100 and ignore leading zeros
</span><span>$ echo </span><span style="color:#d07711;">"$s" </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">-</span><span>woP </span><span style="color:#d07711;">'0*+(\d{3,})' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'"$1"' </span><span style="color:#72ab00;">|</span><span> paste </span><span style="color:#72ab00;">-</span><span>sd,
</span><span style="color:#d07711;">"501"</span><span>,</span><span style="color:#d07711;">"154"</span><span>,</span><span style="color:#d07711;">"98234"
</span></code></pre>
<br>
<h2 id="fixed-string-matching">Fixed string matching<a class="zola-anchor" href="#fixed-string-matching" aria-label="Anchor link for: fixed-string-matching">🔗</a></h2>
<p>Like <code>grep</code>, the <code>-F</code> option will allow fixed strings to be matched, a handy option that I feel every search and replace tool should provide.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'2.3/[4]*6\nfoo\n5.3-[4]*9\n' </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">F </span><span style="color:#d07711;">'[4]*' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'2'
</span><span style="color:#b3933a;">2.3</span><span style="color:#72ab00;">/</span><span style="color:#b3933a;">26
</span><span>foo
</span><span style="color:#b3933a;">5.3</span><span style="color:#72ab00;">-</span><span style="color:#b3933a;">29
</span></code></pre>
<p><code>-F</code> doesn't extend to replacement section though, so you need <code>$$</code> instead of <code>$</code> character to represent it literally.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ echo </span><span style="color:#d07711;">'a.*{2}-b' </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">F </span><span style="color:#d07711;">'.*{2}' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'+$x\tc'
</span><span>a</span><span style="color:#72ab00;">+</span><span>\tc</span><span style="color:#72ab00;">-</span><span>b
</span><span>$ echo </span><span style="color:#d07711;">'a.*{2}-b' </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">F </span><span style="color:#d07711;">'.*{2}' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'+$$x\tc'
</span><span>a</span><span style="color:#72ab00;">+</span><span style="color:#5597d6;">$x</span><span>\tc</span><span style="color:#72ab00;">-</span><span>b
</span></code></pre>
<br>
<h2 id="multiline-matching">Multiline matching<a class="zola-anchor" href="#multiline-matching" aria-label="Anchor link for: multiline-matching">🔗</a></h2>
<p>Another handy option is <code>-U</code> which enables multiline matching.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span>$ s=</span><span style="color:#d07711;">'hi there\nhave a nice day\nbye'
</span><span>
</span><span style="color:#7f8989;"># (?s) flag will allow . to match newline characters as well
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">%b</span><span style="color:#d07711;">' "$s" </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">U </span><span style="color:#d07711;">'(?s)the.*ice' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">''
</span><span>hi day
</span><span>bye
</span></code></pre>
<p><img src="/images/info.svg" alt="info" /> See <a href="https://learnbyexample.github.io/multiline-search-and-replace/">my blog post</a> for a detailed discussion on multiline fixed string search and replace operations from the command line.</p>
<br>
<h2 id="handling-dos-style-input">Handling dos-style input<a class="zola-anchor" href="#handling-dos-style-input" aria-label="Anchor link for: handling-dos-style-input">🔗</a></h2>
<p><code>rg</code> provides support for dos-style files with <code>--crlf</code> option.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># same as: sed -E 's/\w+(\r?)$/xyz\1/'
</span><span style="color:#7f8989;"># note that output will retain CR+LF as line ending
</span><span style="color:#7f8989;"># similar to the sed solution, this will work for unix-style input too
</span><span>$ </span><span style="color:#b39f04;">printf </span><span style="color:#d07711;">'hi there\r\ngood day\r\n' </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#72ab00;">--</span><span>crlf </span><span style="color:#d07711;">'\w+$' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'xyz'
</span><span>hi xyz
</span><span>good xyz
</span></code></pre>
<br>
<h2 id="speed-comparison-with-gnu-sed">Speed comparison with GNU sed<a class="zola-anchor" href="#speed-comparison-with-gnu-sed" aria-label="Anchor link for: speed-comparison-with-gnu-sed">🔗</a></h2>
<p>Another advantage of <code>rg</code> is that it is likely to be faster than <code>sed</code>. See <a href="https://blog.burntsushi.net/ripgrep/">ripgrep benchmark with other grep implementations</a> by the author for a methodological detailed analysis and insights.</p>
<pre data-lang="ruby" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ruby "><code class="language-ruby" data-lang="ruby"><span style="color:#7f8989;"># for small files, initial processing time of rg is a large component
</span><span>$ time echo </span><span style="color:#d07711;">'aba' </span><span style="color:#72ab00;">|</span><span> sed </span><span style="color:#d07711;">'s/a/b/g' </span><span style="color:#72ab00;">></span><span> f1
</span><span>real 0m0.002s
</span><span>$ time echo </span><span style="color:#d07711;">'aba' </span><span style="color:#72ab00;">|</span><span> rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#d07711;">'a' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'b' </span><span style="color:#72ab00;">></span><span> f2
</span><span>real 0m0.007s
</span><span>
</span><span style="color:#7f8989;"># for larger files, rg is likely to be faster
</span><span style="color:#7f8989;"># 6.2M sample ASCII file
</span><span>$ wget </span><span style="color:#d07711;">'https://norvig.com/big.txt'
</span><span>$ time </span><span style="color:#c23f31;">LC_ALL</span><span style="color:#72ab00;">=</span><span style="color:#5597d6;">C</span><span> sed </span><span style="color:#d07711;">'s/\bcat\b/dog/g'</span><span> big.txt </span><span style="color:#72ab00;">></span><span> f1
</span><span>real 0m0.060s
</span><span>$ time rg </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#d07711;">'\bcat\b' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'dog'</span><span> big.txt </span><span style="color:#72ab00;">></span><span> f2
</span><span>real 0m0.048s
</span><span>$ diff </span><span style="color:#72ab00;">-</span><span>s f1 f2
</span><span style="color:#5597d6;">Files</span><span> f1 </span><span style="color:#72ab00;">and</span><span> f2 are identical
</span><span>
</span><span style="color:#7f8989;"># nearly 8 times faster!!
</span><span>$ time </span><span style="color:#c23f31;">LC_ALL</span><span style="color:#72ab00;">=</span><span style="color:#5597d6;">C</span><span> sed </span><span style="color:#72ab00;">-</span><span style="color:#5597d6;">E </span><span style="color:#d07711;">'s/\b(\w+)(\s+\1)+\b/\1/g'</span><span> big.txt </span><span style="color:#72ab00;">></span><span> f1
</span><span>real 0m0.725s
</span><span>$ time rg </span><span style="color:#72ab00;">--</span><span>no</span><span style="color:#72ab00;">-</span><span>unicode </span><span style="color:#72ab00;">--</span><span>passthru </span><span style="color:#72ab00;">-</span><span>wP </span><span style="color:#d07711;">'(\w+)(\s+\1)+' </span><span style="color:#72ab00;">-</span><span>r </span><span style="color:#d07711;">'$1'</span><span> big.txt </span><span style="color:#72ab00;">></span><span> f2
</span><span>real 0m0.093s
</span><span>$ diff </span><span style="color:#72ab00;">-</span><span>s f1 f2
</span><span style="color:#5597d6;">Files</span><span> f1 </span><span style="color:#72ab00;">and</span><span> f2 are identical
</span></code></pre>
<br>
<h2 id="other-alternatives-for-sed">Other alternatives for sed<a class="zola-anchor" href="#other-alternatives-for-sed" aria-label="Anchor link for: other-alternatives-for-sed">🔗</a></h2>
<ul>
<li><a href="https://unix.stackexchange.com/questions/112023/how-can-i-replace-a-string-in-a-files/251742#251742">rpl</a> — search and replace tool, has interesting options like interactive mode and recursive mode</li>
<li><a href="https://github.com/chmln/sd">sd</a> — simple search and replace, implemented in Rust</li>
<li><a href="https://www.perl.org/">perl</a> and <a href="https://www.ruby-lang.org/en/">ruby</a> — programming languages with excellent command line support</li>
</ul>
I know Python basics, what next?2020-07-25T00:00:00+00:002025-05-15T00:00:00+00:00https://learnbyexample.github.io/python-intermediate/<p align="center"><img src="/images/python_what_next.png" alt="Python what next" /></p>
<p align="center"><i>Poster created using <a href="https://www.canva.com/">Canva</a></i></p>
<span id="continue-reading"></span><br>
<h2 id="next-step">Next step<a class="zola-anchor" href="#next-step" aria-label="Anchor link for: next-step">🔗</a></h2>
<p>Programmers often wonder what to do after learning the basics. <a href="https://old.reddit.com/r/learnpython/search?q=what+next&restrict_sr=on">Searching for <code>what next</code> on /r/learnpython</a> will give you too many results. And here are some wonderful articles related to this topic:</p>
<ul>
<li><a href="https://www.devdungeon.com/content/i-know-how-program-i-dont-know-what-program">I know how to program, but I don't know what to program</a></li>
<li><a href="https://www.flyingmachinestudios.com/programming/learn-programming-languages-efficiently/">Techniques for Efficiently Learning Programming Languages</a></li>
<li><a href="https://www.techinasia.com/talk/27-things-started-programming">Things you might encounter in your programming journey</a></li>
</ul>
<p>I do not have a simple answer to this question. However, I'll list a few topics along with resources that might help you take the next step in your Python learning journey.</p>
<br>
<h2 id="exercises">Exercises<a class="zola-anchor" href="#exercises" aria-label="Anchor link for: exercises">🔗</a></h2>
<p>If you feel comfortable with programming basics and Python syntax, then exercises are a good way to test your knowledge. The resource you used to learn Python will typically have some sort of exercises, so those would be ideal as a first choice. I'd also suggest using the below resources to improve your skills. If you get stuck, reread the material related to those topics, search online, ask for clarifications, etc — in short, make an effort to solve it. It is okay to skip some troublesome problems (and come back to it later if you have the time), but you should be able to solve most of the beginner problems. Maintaining notes and cheatsheets will help too, especially for common mistakes.</p>
<ul>
<li><a href="https://exercism.org/tracks/python/exercises">Exercism</a>, <a href="https://www.hackinscience.org/exercises/">Hackinscience</a> and <a href="https://www.practicepython.org/">Practicepython</a> — these are all beginner friendly and difficulty levels are marked</li>
<li><a href="https://github.com/learnbyexample/TUI-apps/tree/main/PythonExercises">Python Exercises</a> — my interactive TUI app, suited for beginner to intermediate level Python learners</li>
<li><a href="https://inventwithpython.com/pythongently/">Python Programming Exercises, Gently Explained</a> — includes gentle explanations of the problem, the prerequisite coding concepts you'll need to understand the solution, etc</li>
<li><a href="https://adventofcode.com/">Adventofcode</a>, <a href="https://www.codewars.com/">Codewars</a>, <a href="https://www.pythonmorsels.com/">Python Morsels</a> — includes more challenging exercises for intermediate to advanced level users</li>
<li><a href="https://py.checkio.org/">Checkio</a>, <a href="https://www.codingame.com/start">Codingame</a> — gaming based challenges</li>
<li><a href="https://old.reddit.com/r/dailyprogrammer/">/r/dailyprogrammer</a> — interesting challenges</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> See also this article on <a href="https://www.pythonmorsels.com/programming-exercise-tips/">solving programming exercises</a>.</p>
<br>
<h2 id="projects">Projects<a class="zola-anchor" href="#projects" aria-label="Anchor link for: projects">🔗</a></h2>
<p>Once you are comfortable with basics and syntax, the next step is projects. I wrote a 10-line program that solved a common problem for me — adding <code>body { text-align: justify }</code> to <code>epub</code> files that are not justify aligned. I didn't know that this line would help beforehand. Found a solution online and then automated the process of unzipping <code>epub</code>, adding the line and then packing it again. That will likely need you to lookup documentation and go through some stackoverflow Q&A as well. And once you have written the solution and use it regularly, you'll likely encounter corner cases and features to be added. I feel this is a great way to learn and understand programming.</p>
<p>These days, I use a better EPUB reader that allows me to customize alignments. Here's another real world example. I'm on Linux and use the terminal for many things. I wanted a CLI tool to do simple calculations. There's <code>bc</code> command, but it doesn't accept direct string argument and you need to set <code>scale</code> and so on. So, I looked up how to write a CLI tool in Python and <a href="https://learnbyexample.github.io/practice_python_projects/calculator/calculator.html">wrote one using the built-in <code>argparse</code> module</a> that works for my particular use cases.</p>
<p>Here are some resources to help you get started on projects:</p>
<ul>
<li><a href="https://github.com/karan/Projects-Solutions">Projects with solutions</a> — algorithms, data structures, networking, security, databases, etc</li>
<li><a href="https://github.com/practical-tutorials/project-based-learning#python">Project based learning</a> — web applications, bots, data science, machine learning, etc</li>
<li><a href="https://github.com/norvig/pytudes">Pytudes by Peter Norvig</a> — Python programs, usually short, of considerable difficulty</li>
<li>Books:
<ul>
<li><a href="https://inventwithpython.com/bigbookpython/">The Big Book of Small Python Projects</a></li>
<li><a href="https://www.manning.com/books/tiny-python-projects">Tiny Python Projects</a></li>
<li><a href="https://practicalpython.yasoob.me/toc.html">Practical Python Projects</a></li>
<li><a href="https://nostarch.com/real-world-python">Real world Python</a></li>
<li><a href="https://learnbyexample.github.io/practice_python_projects/">Practice Python Projects</a> — my book on beginner to intermediate level projects</li>
</ul>
</li>
<li><a href="https://old.reddit.com/r/learnpython/comments/k5k1h0/what_do_you_automate_with_python_at_home/">/r/learnpython: What do you automate with Python at home?</a></li>
<li><a href="https://projectbook.code.brettchalupa.com/">Projectbook</a> — collection of over 100 software project ideas for people looking to learn a given language or technology</li>
</ul>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://goodresearch.dev/">The Good Research Code Handbook</a> to learn how to organize your code so that it is easy to understand and works reliably.</p>
<br>
<h2 id="debugging">Debugging<a class="zola-anchor" href="#debugging" aria-label="Anchor link for: debugging">🔗</a></h2>
<p>Knowing how to debug your programs is crucial and should be ideally taught right from the beginning instead of a chapter at the end of the book. <a href="https://greenteapress.com/wp/think-python-2e/">Think Python</a> is an awesome example for such a resource material.</p>
<p>Sites like <a href="https://www.pythontutor.com/visualize.html#mode=edit">Pythontutor</a> allow you to visually debug a program — you can execute a program step by step and see the current value of variables. Similar feature is typically provided by IDEs like <a href="https://www.jetbrains.com/pycharm/">Pycharm</a> and <a href="https://thonny.org/">Thonny</a>. Under the hood, these visualizations are using the <a href="https://docs.python.org/3/library/pdb.html">pdb module</a>. See also <a href="https://realpython.com/python-debugging-pdb/">Python debugging with pdb</a>.</p>
<p>Debugging is often a frustrating experience. Taking a break helps (and sometimes I find the solution or spot a problem in my dreams). Try to reduce the code as much as possible so that you are left with minimal code necessary to reproduce the issue. Talking about the problem to a friend/colleague/inanimate-objects/etc can help too — known as <a href="https://rubberduckdebugging.com/">Rubber duck debugging</a>. I have often found the issue while formulating a question to be asked on forums like stackoverflow/reddit because writing down your problem is another way to bring clarity than just having a vague idea in your mind. Here's some more articles on this challenging topic:</p>
<ul>
<li><a href="https://jvns.ca/blog/2019/06/23/a-few-debugging-resources/">What does debugging a program look like?</a></li>
<li><a href="https://thepythoncodingbook.com/2022/04/17/debugging-python-code-is-like-detective-work-lets-investigate/">Debugging Python code is like detective work</a></li>
<li><a href="https://ericlippert.com/2014/03/05/how-to-debug-small-programs/">How to debug small programs</a></li>
<li><a href="https://uchicago-cs.github.io/debugging-guide/">Debugging guide</a></li>
<li><a href="https://ryanstutorials.net/problem-solving-skills/">Problem solving skills</a></li>
</ul>
<p>Here's a summarized snippet from a collection of <a href="https://stackoverflow.com/q/169713/4082052">interesting bug stories</a>.</p>
<blockquote>
<p>A jpeg parser choked whenever the CEO came into the room, because he always had a shirt with a square pattern on it, which triggered some special case of contrast and block boundary algorithms.</p>
</blockquote>
<p><img src="/images/info.svg" alt="info" /> See also <a href="https://500mile.email/">this curated list of absurd software bug stories</a>.</p>
<br>
<h2 id="testing">Testing<a class="zola-anchor" href="#testing" aria-label="Anchor link for: testing">🔗</a></h2>
<p>Another crucial aspect in the programming journey is knowing how to write tests. In bigger projects, usually there are separate engineers (often in much larger number than code developers) to test the code. Even in those cases, writing a few sanity test cases yourself can help you develop faster knowing that the changes aren't breaking basic functionality.</p>
<p>There's no single consensus on test methodologies. There is <a href="https://en.wikipedia.org/wiki/Unit_testing">Unit testing</a>, <a href="https://en.wikipedia.org/wiki/Integration_testing">Integration testing</a>, <a href="https://en.wikipedia.org/wiki/Test-driven_development">Test-driven development</a> and so on. Often, a combination of these is used. These days, machine learning is also being considered to reduce the testing time, see <a href="https://hacks.mozilla.org/2020/07/testing-firefox-more-efficiently-with-machine-learning/">Testing Firefox more efficiently with machine learning</a> for example.</p>
<p>When I start a project, I usually try to write the programs incrementally. Say I need to iterate over files from a directory. I will make sure that portion is working (usually with <code>print</code> statements), then add another feature — say file reading and test that and so on. This reduces the burden of testing a large program at once at the end. And depending upon the nature of the program, I'll add a few sanity tests at the end. For example, for my <a href="https://github.com/learnbyexample/command_help">command_help</a> project, I copy pasted a few test runs of the program with different options and arguments into a separate file and wrote a program to perform these tests programmatically whenever the source code is modified.</p>
<p>For non-trivial projects, you'll usually end up needing frameworks like built-in module <code>unittest</code> or third-party modules like <code>pytest</code>. Here's some learning resources.</p>
<ul>
<li><a href="https://realpython.com/python-testing/">Getting started with testing in Python</a></li>
<li><a href="https://blog.thea.codes/my-python-testing-style-guide/">Python testing style guide</a></li>
<li><a href="https://www.thedigitalcatonline.com/blog/2020/09/10/tdd-in-python-with-pytest-part-1/">TDD in Python with pytest</a></li>
<li><a href="https://www.obeythetestinggoat.com/">obeythetestinggoat</a> — TDD for the Web, with Python, Selenium, Django, JavaScript and pals</li>
<li><a href="https://testdriven.io/blog/modern-tdd/">Modern Test-Driven Development in Python</a> — TDD guide, has a real world application example</li>
</ul>
<br>
<h2 id="intermediate-to-advanced-python-resources">Intermediate to Advanced Python resources<a class="zola-anchor" href="#intermediate-to-advanced-python-resources" aria-label="Anchor link for: intermediate-to-advanced-python-resources">🔗</a></h2>
<p><strong>Intermediate</strong></p>
<ul>
<li><a href="https://docs.python.org/3/index.html">Official Python docs</a> — Python docs are a treasure trove of information</li>
<li><a href="https://mathspp.gumroad.com/l/pydonts">Pydon'ts</a> — Write elegant Python code, make the best use of the core Python features</li>
<li><a href="https://calmcode.io/">Calmcode</a> — videos on testing, code style, args kwargs, data science, etc</li>
<li><a href="https://dabeaz-course.github.io/practical-python/Notes/Contents.html">Practical Python Programming</a> — covers foundational aspects of Python programming with an emphasis on script writing, data manipulation, and program organization</li>
<li><a href="https://inventwithpython.com/beyond/chapter0.html">Beyond the Basic Stuff with Python</a> — Best Practices, Tools, and Techniques, OOP, Practice Projects</li>
<li><a href="https://www.oreilly.com/library/view/python-distilled/9780134173399/">Python Distilled</a> — this pragmatic guide provides a concise narrative related to fundamental programming topics such as data abstraction, control flow, program structure, functions, objects, and modules</li>
<li><a href="https://www.oreilly.com/library/view/python-in-a/9781098113544/">Python in a Nutshell</a> — use modern Python idiomatically, structure Python projects, how to debug</li>
</ul>
<p><strong>Algorithms and Design patterns</strong></p>
<ul>
<li><a href="https://runestone.academy/ns/books/published/pythonds3/index.html">Problem solving with algorithms and data structures</a> </li>
<li><a href="https://github.com/faif/python-patterns">GitHub: Collection of design patterns and idioms</a></li>
<li><a href="https://www.thedigitalcatbooks.com/pycabook-introduction/">Clean Architectures in Python</a> — software design methodology</li>
</ul>
<p><strong>Advanced</strong></p>
<ul>
<li><a href="https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/">Fluent Python</a> — takes you through Python's core language features and libraries, and shows you how to make your code shorter, faster, and more readable at the same time</li>
<li><a href="https://nostarch.com/seriouspython">Serious Python</a> — deployment, scalability, testing, and more</li>
<li><a href="https://www.manning.com/books/practices-of-the-python-pro">Practices of the Python Pro</a> — learn to design professional-level, clean, easily maintainable software at scale, includes examples for software development best practices</li>
<li><a href="https://github.com/dabeaz-course/python-mastery">Advanced Python Mastery</a> — exercise-driven course on Advanced Python Programming that was battle-tested several hundred times on the corporate-training circuit for more than a decade</li>
</ul>
<br>
<h2 id="handy-cheatsheets">Handy cheatsheets<a class="zola-anchor" href="#handy-cheatsheets" aria-label="Anchor link for: handy-cheatsheets">🔗</a></h2>
<ul>
<li><a href="https://ehmatthes.github.io/pcc_3e/cheat_sheets/">Python Crash Course cheatsheet</a></li>
<li><a href="https://gto76.github.io/python-cheatsheet/">Comprehensive Python cheatsheet</a></li>
<li><a href="https://ipgp.github.io/scientific_python_cheat_sheet/">Scientific Python cheatsheet</a></li>
<li><a href="https://pythonforbiologists.com/29-common-beginner-errors-on-one-page.html">Common beginner errors</a></li>
<li><a href="https://learnbyexample.github.io/python-regex-cheatsheet/">Python regular expression cheatsheet</a> — my blog post, includes examples as well</li>
</ul>
<br>
<h2 id="more-python-resources">More Python resources<a class="zola-anchor" href="#more-python-resources" aria-label="Anchor link for: more-python-resources">🔗</a></h2>
<p>Inspired by this post, I made a <a href="https://learnbyexample.github.io/py_resources/">Python learning resources repository</a> which is categorized (beginner, intermediate, advanced, domains like web/ML/data science, etc) and includes a handy search feature.</p>
<br>
<p>I hope these resources will help you take that crucial next step and continue your Python journey. Happy learning :)</p>
JavaScript regular expressions cheatsheet and examples2020-07-20T00:00:00+00:002023-10-27T00:00:00+00:00https://learnbyexample.github.io/javascript-regexp-cheatsheet/<p align="center"><img src="/images/books/js_regexp_example.png" alt="sample railroad diagram of a regexp" /></p>
<p><em>Above diagram created using <a href="https://jex.im/regulex">Regulex</a></em></p>
<span id="continue-reading"></span><br>
<p>This blog post gives an overview of regular expression syntax and features supported by JavaScript. Examples have been tested on the Chrome/Chromium console and includes features not available in other browsers and platforms. This post is an excerpt from my <a href="https://github.com/learnbyexample/learn_js_regexp">Understanding JavaScript RegExp</a> book.</p>
<h2 id="elements-that-define-a-regular-expression">Elements that define a regular expression<a class="zola-anchor" href="#elements-that-define-a-regular-expression" aria-label="Anchor link for: elements-that-define-a-regular-expression">🔗</a></h2>
<table><thead><tr><th>Note</th><th>Description</th></tr></thead><tbody>
<tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions">MDN: Regular Expressions</a></td><td>MDN reference for JavaScript regular expressions</td></tr>
<tr><td><code>/pat/</code></td><td>a RegExp object</td></tr>
<tr><td><code>const pet = /dog/</code></td><td>save regexp in a variable for reuse, clarity, etc</td></tr>
<tr><td><code>/pat/.test(s)</code></td><td>check if the pattern is present anywhere in the input string</td></tr>
<tr><td></td><td>returns <code>true</code> or <code>false</code></td></tr>
<tr><td><code>i</code></td><td>flag to ignore case when matching alphabets</td></tr>
<tr><td><code>g</code></td><td>flag to match all occurrences</td></tr>
<tr><td><code>new RegExp('pat', 'i')</code></td><td>construct RegExp from a string</td></tr>
<tr><td></td><td>optional second argument specifies flags</td></tr>
<tr><td></td><td>use backtick strings with <code>${}</code> for interpolation</td></tr>
<tr><td><code>source</code></td><td>property to convert a RegExp object to a string</td></tr>
<tr><td></td><td>helps to insert a RegExp inside another RegExp</td></tr>
<tr><td><code>flags</code></td><td>property to get flags of a RegExp object</td></tr>
<tr><td><code>s.replace(/pat/, 'repl')</code></td><td>method for search and replace</td></tr>
<tr><td><code>s.search(/pat/)</code></td><td>gives the starting location of the match or <code>-1</code></td></tr>
<tr><td><code>s.split(/pat/)</code></td><td>split a string based on regexp</td></tr>
</tbody></table>
<br>
<table><thead><tr><th>Anchors</th><th>Description</th></tr></thead><tbody>
<tr><td><code>^</code></td><td>restricts the match to the start of string</td></tr>
<tr><td><code>$</code></td><td>restricts the match to the end of string</td></tr>
<tr><td><code>m</code></td><td>flag to match the start/end of line with <code>^</code> and <code>$</code> anchors</td></tr>
<tr><td></td><td><code>\r</code>, <code>\n</code>, <code>\u2028</code> and <code>\u2029</code> are line separators</td></tr>
<tr><td></td><td>DOS-style files use <code>\r\n</code>, may need special attention</td></tr>
<tr><td><code>\b</code></td><td>restricts the match to the start and end of words</td></tr>
<tr><td></td><td>word characters: alphabets, digits, underscore</td></tr>
<tr><td><code>\B</code></td><td>matches wherever <code>\b</code> doesn't match</td></tr>
</tbody></table>
<p><code>^</code>, <code>$</code> and <code>\</code> are <strong>metacharacters</strong> in the above table, as these characters have a special meaning. Prefix a <code>\</code> character to remove the special meaning and match such characters literally. For example, <code>\^</code> will match a <code>^</code> character instead of acting as an anchor.</p>
<br>
<table><thead><tr><th>Feature</th><th>Description</th></tr></thead><tbody>
<tr><td><code>pat1|pat2|pat3</code></td><td>multiple regexp combined as conditional OR</td></tr>
<tr><td></td><td>each alternative can have independent anchors</td></tr>
<tr><td><code>(pat)</code></td><td>group pattern(s), also a capturing group</td></tr>
<tr><td><code>a(b|c)d</code></td><td>same as <code>abd|acd</code></td></tr>
<tr><td><code>(?:pat)</code></td><td>non-capturing group</td></tr>
<tr><td><code>(?<name>pat)</code></td><td>named capture group</td></tr>
<tr><td><code>.</code></td><td>match any character except line separators</td></tr>
<tr><td><code>s</code></td><td>flag to match line separators as well</td></tr>
<tr><td><code>[]</code></td><td>character class, matches one character among many</td></tr>
</tbody></table>
<p>Alternation precedence: pattern which matches earliest in the input gets higher priority. Tie-breaker is left-to-right if matches have the same starting location.</p>
<br>
<table><thead><tr><th>Greedy Quantifiers</th><th>Description</th></tr></thead><tbody>
<tr><td><code>?</code></td><td>match <code>0</code> or <code>1</code> times</td></tr>
<tr><td><code>*</code></td><td>match <code>0</code> or more times</td></tr>
<tr><td><code>+</code></td><td>match <code>1</code> or more times</td></tr>
<tr><td><code>{m,n}</code></td><td>match <code>m</code> to <code>n</code> times</td></tr>
<tr><td><code>{m,}</code></td><td>match at least <code>m</code> times</td></tr>
<tr><td><code>{n}</code></td><td>match exactly <code>n</code> times</td></tr>
<tr><td><code>pat1.*pat2</code></td><td>any number of characters between <code>pat1</code> and <code>pat2</code></td></tr>
<tr><td><code>pat1.*pat2|pat2.*pat1</code></td><td>match both <code>pat1</code> and <code>pat2</code> in any order</td></tr>
</tbody></table>
<p><strong>Greedy</strong> here means that the above quantifiers will match as much as possible that'll also honor the overall regexp. Appending a <code>?</code> to greedy quantifiers makes them <strong>non-greedy</strong>, i.e. match as <em>minimally</em> as possible. Quantifiers can be applied to literal characters, groups, backreferences and character classes.</p>
<br>
<table><thead><tr><th>Character class</th><th>Description</th></tr></thead><tbody>
<tr><td><code>[ae;o]</code></td><td>match any of these characters once</td></tr>
<tr><td><code>[3-7]</code></td><td>range of characters from <code>3</code> to <code>7</code></td></tr>
<tr><td><code>[^=b2]</code></td><td>negated set, match other than <code>=</code> or <code>b</code> or <code>2</code></td></tr>
<tr><td><code>[a-z-]</code></td><td><code>-</code> should be the first/last or escaped using <code>\</code> to match literally</td></tr>
<tr><td><code>[+^]</code></td><td><code>^</code> shouldn't be the first character or escaped using <code>\</code></td></tr>
<tr><td><code>[\]\\]</code></td><td><code>]</code> and <code>\</code> should be escaped using <code>\</code></td></tr>
<tr><td></td><td><code>[</code> doesn't need escaping, but <code>\[</code> can also be used</td></tr>
<tr><td><code>\w</code></td><td>similar to <code>[A-Za-z0-9_]</code> for matching word characters</td></tr>
<tr><td><code>\d</code></td><td>similar to <code>[0-9]</code> for matching digit characters</td></tr>
<tr><td><code>\s</code></td><td>similar to <code>[ \t\n\r\f\v]</code> for matching whitespace characters</td></tr>
<tr><td></td><td>use <code>\W</code>, <code>\D</code>, and <code>\S</code> for their opposites respectively</td></tr>
<tr><td><code>u</code></td><td>flag to enable unicode matching</td></tr>
<tr><td><code>v</code></td><td>superset of <code>u</code> flag, enables additional features</td></tr>
<tr><td><code>\p{}</code></td><td>Unicode character sets</td></tr>
<tr><td><code>\P{}</code></td><td>negated Unicode character sets</td></tr>
<tr><td></td><td>see <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Unicode_character_class_escape">MDN: Unicode character class escape</a> for details</td></tr>
<tr><td><code>\u{}</code></td><td>specify Unicode characters using codepoints</td></tr>
</tbody></table>
<br>
<table><thead><tr><th>Lookarounds</th><th>Description</th></tr></thead><tbody>
<tr><td>lookarounds</td><td>create custom positive/negative assertions</td></tr>
<tr><td></td><td>zero-width like anchors and not part of matching portions</td></tr>
<tr><td><code>(?!pat)</code></td><td>negative lookahead assertion</td></tr>
<tr><td><code>(?<!pat)</code></td><td>negative lookbehind assertion</td></tr>
<tr><td><code>(?=pat)</code></td><td>positive lookahead assertion</td></tr>
<tr><td><code>(?<=pat)</code></td><td>positive lookbehind assertion</td></tr>
<tr><td></td><td>variable length lookbehind is allowed</td></tr>
<tr><td><code>(?!pat1)(?=pat2)</code></td><td>multiple assertions can be specified next to each other in any order</td></tr>
<tr><td></td><td>as they mark a matching location without consuming characters</td></tr>
<tr><td><code>((?!pat).)*</code></td><td>Negates a regexp pattern</td></tr>
</tbody></table>
<br>
<table><thead><tr><th>Matched portion</th><th>Description</th></tr></thead><tbody>
<tr><td><code>m = s.match(/pat/)</code></td><td>assuming the <code>g</code> flag isn't used and regexp succeeds,</td></tr>
<tr><td></td><td>returns an array with the matched portion and 3 properties</td></tr>
<tr><td></td><td><code>index</code> property gives the starting location of the match</td></tr>
<tr><td></td><td><code>input</code> property gives the input string <code>s</code></td></tr>
<tr><td></td><td><code>groups</code> property gives dictionary of named capture groups</td></tr>
<tr><td><code>m[0]</code></td><td>for the above case, gives the entire matched portion</td></tr>
<tr><td><code>m[N]</code></td><td>matched portion of the Nth capture group</td></tr>
<tr><td><code>d</code></td><td>flag to get the starting and ending locations of the matching portions via the <code>indices</code> property</td></tr>
<tr><td><code>s.match(/pat/g)</code></td><td>returns only the matched portions, no properties</td></tr>
<tr><td><code>s.matchAll(/pat/g)</code></td><td>returns an iterator containing details for each matched portion and its properties</td></tr>
<tr><td>Backreference</td><td>gives the matched portion of the Nth capture group</td></tr>
<tr><td></td><td>use <code>$1</code>, <code>$2</code>, <code>$3</code>, etc in the replacement section</td></tr>
<tr><td></td><td><code>$&</code> gives the entire matched portion</td></tr>
<tr><td></td><td><code>$`</code> gives the string before the matched portion</td></tr>
<tr><td></td><td><code>$'</code> gives the string after the matched portion</td></tr>
<tr><td></td><td>use <code>\1</code>, <code>\2</code>, <code>\3</code>, etc within the regexp definition</td></tr>
<tr><td><code>$$</code></td><td>insert <code>$</code> literally in the replacement section</td></tr>
<tr><td><code>$0N</code></td><td>same as <code>$N</code>, allows to separate backreference and other digits</td></tr>
<tr><td><code>\N\xhh</code></td><td>allows to separate backreference and digits in the regexp definition</td></tr>
<tr><td><code>(?<name>pat)</code></td><td>named capture group</td></tr>
<tr><td></td><td>use <code>\k<name></code> for backreferencing in the regexp definition</td></tr>
<tr><td></td><td>use <code>$<name></code> for backreferencing in the replacement section</td></tr>
</tbody></table>
<br>
<h2 id="regular-expression-examples">Regular expression examples<a class="zola-anchor" href="#regular-expression-examples" aria-label="Anchor link for: regular-expression-examples">🔗</a></h2>
<ul>
<li><code>test()</code> method</li>
</ul>
<pre data-lang="ts" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#72ab00;">> </span><span style="color:#668f14;">let </span><span style="color:#5597d6;">sentence </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'This is a sample string'
</span><span>
</span><span style="color:#72ab00;">> </span><span style="color:#c49a39;">/is/</span><span>.</span><span style="color:#b39f04;">test</span><span>(</span><span style="color:#5597d6;">sentence</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#b3933a;">true
</span><span style="color:#72ab00;">> </span><span style="color:#c49a39;">/xyz/</span><span>.</span><span style="color:#b39f04;">test</span><span>(</span><span style="color:#5597d6;">sentence</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#b3933a;">false
</span><span>
</span><span style="color:#72ab00;">> if </span><span>(</span><span style="color:#c49a39;">/ring/</span><span>.</span><span style="color:#b39f04;">test</span><span>(</span><span style="color:#5597d6;">sentence</span><span>)) {
</span><span> </span><span style="color:#a2a001;">console</span><span>.</span><span style="color:#b39f04;">log</span><span>(</span><span style="color:#d07711;">'mission success'</span><span>)
</span><span> }
</span><span style="color:#72ab00;">< </span><span style="color:#5597d6;">mission success
</span></code></pre>
<ul>
<li><code>new RegExp()</code> constructor</li>
</ul>
<pre data-lang="ts" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#72ab00;">> new </span><span style="color:#c23f31;">RegExp</span><span>(</span><span style="color:#d07711;">'dog'</span><span>, </span><span style="color:#d07711;">'i'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#c49a39;">/dog/</span><span style="color:#72ab00;">i
</span><span>
</span><span style="color:#72ab00;">> new </span><span style="color:#c23f31;">RegExp</span><span>(</span><span style="color:#d07711;">'123</span><span style="color:#aeb52b;">\\</span><span style="color:#d07711;">tabc'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#c49a39;">/123</span><span style="color:#aeb52b;">\t</span><span style="color:#c49a39;">abc/
</span><span>
</span><span style="color:#72ab00;">> </span><span style="color:#668f14;">let </span><span style="color:#5597d6;">greeting </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'hi'
</span><span style="color:#72ab00;">> new </span><span style="color:#c23f31;">RegExp</span><span>(</span><span style="color:#d07711;">`${</span><span style="color:#acb3c2;">greeting</span><span style="color:#d07711;">.</span><span style="color:#b39f04;">toUpperCase</span><span style="color:#d07711;">()} there`</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#c49a39;">/HI there/
</span></code></pre>
<ul>
<li>string and line anchors</li>
</ul>
<pre data-lang="ts" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#7f8989;">// string anchors
</span><span style="color:#72ab00;">> </span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">^</span><span style="color:#c49a39;">cat/</span><span>.</span><span style="color:#b39f04;">test</span><span>(</span><span style="color:#d07711;">'cater'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#b3933a;">true
</span><span style="color:#72ab00;">> </span><span>[</span><span style="color:#d07711;">'surrender'</span><span>, </span><span style="color:#d07711;">'newer'</span><span>, </span><span style="color:#d07711;">'door'</span><span>].</span><span style="color:#c23f31;">filter</span><span>(</span><span style="color:#5597d6;">w </span><span style="color:#668f14;">=> </span><span style="color:#c49a39;">/er</span><span style="color:#72ab00;">$</span><span style="color:#c49a39;">/</span><span>.</span><span style="color:#b39f04;">test</span><span>(</span><span style="color:#5597d6;">w</span><span>))
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#d07711;">'surrender'</span><span>, </span><span style="color:#d07711;">'newer'</span><span>]
</span><span>
</span><span style="color:#7f8989;">// use 'm' flag to match at the start/end of each line
</span><span style="color:#72ab00;">> </span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">^</span><span style="color:#c49a39;">par</span><span style="color:#72ab00;">$</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">m</span><span>.</span><span style="color:#b39f04;">test</span><span>(</span><span style="color:#d07711;">'spare</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">par</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">era</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">dare'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#b3933a;">true
</span><span>
</span><span style="color:#7f8989;">// escape metacharacters to match them literally
</span><span style="color:#72ab00;">> </span><span style="color:#c49a39;">/b</span><span style="color:#108f3d;">\^</span><span style="color:#c49a39;">2/</span><span>.</span><span style="color:#b39f04;">test</span><span>(</span><span style="color:#d07711;">'a^2 + b^2 - C*3'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#b3933a;">true
</span></code></pre>
<ul>
<li><code>replace()</code> method and word boundaries</li>
</ul>
<pre data-lang="ts" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#72ab00;">> </span><span style="color:#668f14;">let </span><span style="color:#5597d6;">items </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'catapults</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">concatenate</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">cat'
</span><span style="color:#72ab00;">> </span><span style="color:#a2a001;">console</span><span>.</span><span style="color:#b39f04;">log</span><span>(</span><span style="color:#5597d6;">items</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">^</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">gm</span><span>, </span><span style="color:#d07711;">'* '</span><span>))
</span><span style="color:#72ab00;">< * </span><span style="color:#5597d6;">catapults
</span><span> </span><span style="color:#72ab00;">* </span><span style="color:#5597d6;">concatenate
</span><span> </span><span style="color:#72ab00;">* </span><span style="color:#5597d6;">cat
</span><span>
</span><span style="color:#72ab00;">> </span><span style="color:#668f14;">let </span><span style="color:#5597d6;">sample </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'par spar apparent spare part'
</span><span style="color:#7f8989;">// replace 'par' only at the start of word
</span><span style="color:#72ab00;">> </span><span style="color:#5597d6;">sample</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">\b</span><span style="color:#c49a39;">par/</span><span style="color:#72ab00;">g</span><span>, </span><span style="color:#d07711;">'X'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'X spar apparent spare Xt'
</span><span style="color:#7f8989;">// replace 'par' at the end of word but not whole word 'par'
</span><span style="color:#72ab00;">> </span><span style="color:#5597d6;">sample</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">\B</span><span style="color:#c49a39;">par</span><span style="color:#72ab00;">\b</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">g</span><span>, </span><span style="color:#d07711;">'X'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'par sX apparent spare part'
</span></code></pre>
<ul>
<li>alternations and grouping</li>
</ul>
<pre data-lang="ts" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#7f8989;">// replace either 'cat' at the start of string or 'cat' at the end of word
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'catapults concatenate cat scat'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">^</span><span style="color:#c49a39;">cat</span><span style="color:#72ab00;">|</span><span style="color:#c49a39;">cat</span><span style="color:#72ab00;">\b</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">g</span><span>, </span><span style="color:#d07711;">'X'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'Xapults concatenate X sX'
</span><span>
</span><span style="color:#7f8989;">// same as: /\bpark\b|\bpart\b/g
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'park parked part party'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">\b</span><span style="color:#c49a39;">par(k</span><span style="color:#72ab00;">|</span><span style="color:#c49a39;">t)</span><span style="color:#72ab00;">\b</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">g</span><span>, </span><span style="color:#d07711;">'X'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'X parked X party'
</span></code></pre>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping">MDN: Regular Expressions Guide</a> provides the <code>escapeRegExp()</code> function, useful to automatically escape metacharacters.
<ul>
<li>See also <a href="https://github.com/slevithan/xregexp">XRegExp</a>, provides handy methods like <a href="https://xregexp.com/api/#escape">XRegExp.escape()</a> and <a href="https://xregexp.com/api/#union">XRegExp.union()</a>. The union method has additional functionality of allowing a mix of string and RegExp literals and also takes care of renumbering backreferences.</li>
</ul>
</li>
</ul>
<pre data-lang="ts" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#72ab00;">> </span><span style="color:#668f14;">function </span><span style="color:#c23f31;">escapeRegExp</span><span>(</span><span style="color:#5597d6;">string</span><span>) {
</span><span> </span><span style="color:#72ab00;">return </span><span style="color:#5597d6;">string</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/</span><span style="color:#aeb52b;">[</span><span style="color:#b3933a;">.</span><span style="color:#aeb52b;">*+?^${}()|[</span><span style="color:#108f3d;">\]\\</span><span style="color:#aeb52b;">]</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">g</span><span>, </span><span style="color:#d07711;">'</span><span style="color:#aeb52b;">\\</span><span style="color:#d07711;">$&'</span><span>)
</span><span> }
</span><span>
</span><span style="color:#72ab00;">> </span><span style="color:#668f14;">function </span><span style="color:#c23f31;">unionRegExp</span><span>(</span><span style="color:#5597d6;">arr</span><span>) {
</span><span> </span><span style="color:#72ab00;">return </span><span style="color:#5597d6;">arr</span><span>.</span><span style="color:#c23f31;">map</span><span>(</span><span style="color:#5597d6;">w </span><span style="color:#668f14;">=> </span><span style="color:#c23f31;">escapeRegExp</span><span>(</span><span style="color:#5597d6;">w</span><span>)).</span><span style="color:#b39f04;">join</span><span>(</span><span style="color:#d07711;">'|'</span><span>)
</span><span> }
</span><span>
</span><span style="color:#72ab00;">> new </span><span style="color:#c23f31;">RegExp</span><span>(</span><span style="color:#c23f31;">unionRegExp</span><span>([</span><span style="color:#d07711;">'c^t'</span><span>, </span><span style="color:#d07711;">'dog$'</span><span>, </span><span style="color:#d07711;">'f|x'</span><span>]), </span><span style="color:#d07711;">'g'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#c49a39;">/c</span><span style="color:#108f3d;">\^</span><span style="color:#c49a39;">t</span><span style="color:#72ab00;">|</span><span style="color:#c49a39;">dog</span><span style="color:#108f3d;">\$</span><span style="color:#72ab00;">|</span><span style="color:#c49a39;">f</span><span style="color:#108f3d;">\|</span><span style="color:#c49a39;">x/</span><span style="color:#72ab00;">g
</span></code></pre>
<ul>
<li>dot metacharacter and quantifiers</li>
</ul>
<pre data-lang="ts" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#7f8989;">// matches character '2', any character and then character '3'
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'42</span><span style="color:#aeb52b;">\t</span><span style="color:#d07711;">35'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/2</span><span style="color:#aeb52b;">.</span><span style="color:#c49a39;">3/</span><span>, </span><span style="color:#d07711;">'8'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'485'
</span><span style="color:#7f8989;">// 's' flag will allow line separators to be matched as well
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'Hi there</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">Have a Nice Day'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/the</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*</span><span style="color:#c49a39;">ice/</span><span style="color:#72ab00;">s</span><span>, </span><span style="color:#d07711;">'X'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'Hi X Day'
</span><span>
</span><span style="color:#7f8989;">// same as: /part|parrot|parent/g
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'par part parrot parent'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/par(en</span><span style="color:#72ab00;">|</span><span style="color:#c49a39;">ro)</span><span style="color:#72ab00;">?</span><span style="color:#c49a39;">t/</span><span style="color:#72ab00;">g</span><span>, </span><span style="color:#d07711;">'X'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'par X X X'
</span><span>
</span><span style="color:#72ab00;">> </span><span>[</span><span style="color:#d07711;">'abc'</span><span>, </span><span style="color:#d07711;">'ac'</span><span>, </span><span style="color:#d07711;">'abbc'</span><span>, </span><span style="color:#d07711;">'xabbbcz'</span><span>].</span><span style="color:#c23f31;">filter</span><span>(</span><span style="color:#5597d6;">w </span><span style="color:#668f14;">=> </span><span style="color:#c49a39;">/ab</span><span style="color:#72ab00;">{1,4}</span><span style="color:#c49a39;">c/</span><span>.</span><span style="color:#b39f04;">test</span><span>(</span><span style="color:#5597d6;">w</span><span>))
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#d07711;">'abc'</span><span>, </span><span style="color:#d07711;">'abbc'</span><span>, </span><span style="color:#d07711;">'xabbbcz'</span><span>]
</span></code></pre>
<ul>
<li><code>match()</code> method</li>
</ul>
<pre data-lang="ts" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#7f8989;">// entire matched portion
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'too soon a song snatch'</span><span>.</span><span style="color:#b39f04;">match</span><span>(</span><span style="color:#c49a39;">/so</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">n/</span><span>)[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'soon'
</span><span style="color:#7f8989;">// matched portion of the second capture group
</span><span style="color:#72ab00;">> </span><span style="color:#668f14;">let </span><span style="color:#5597d6;">purchase </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'coffee:100g tea:250g sugar:75g chocolate:50g'
</span><span style="color:#72ab00;">> </span><span style="color:#5597d6;">purchase</span><span>.</span><span style="color:#b39f04;">match</span><span>(</span><span style="color:#c49a39;">/:(</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#c49a39;">)g</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#c49a39;">:(</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#c49a39;">)g</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#c49a39;">chocolate:(</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#c49a39;">)g/</span><span>)[</span><span style="color:#b3933a;">2</span><span>]
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'250'
</span><span>
</span><span style="color:#7f8989;">// starting location of the matching portion
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'cat and dog'</span><span>.</span><span style="color:#b39f04;">match</span><span>(</span><span style="color:#c49a39;">/dog/</span><span>).</span><span style="color:#a2a001;">index
</span><span style="color:#72ab00;">< </span><span style="color:#b3933a;">8
</span><span style="color:#7f8989;">// start and end+1 location of the matching portion
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'awesome'</span><span>.</span><span style="color:#b39f04;">match</span><span>(</span><span style="color:#72ab00;">/</span><span style="color:#5597d6;">so</span><span style="color:#72ab00;">/</span><span style="color:#5597d6;">d</span><span>).</span><span style="color:#5597d6;">indices</span><span>[</span><span style="color:#b3933a;">0</span><span>]
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#b3933a;">3</span><span>, </span><span style="color:#b3933a;">5</span><span>]
</span><span>
</span><span style="color:#7f8989;">// get all matching portions with 'g' flag
</span><span style="color:#7f8989;">// no properties or group portions
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'par spar apparent spare part'</span><span>.</span><span style="color:#b39f04;">match</span><span>(</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">\b</span><span style="color:#c49a39;">s</span><span style="color:#72ab00;">?</span><span style="color:#c49a39;">par</span><span style="color:#aeb52b;">[et]</span><span style="color:#72ab00;">\b</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">g</span><span>)
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#d07711;">'spare'</span><span>, </span><span style="color:#d07711;">'part'</span><span>]
</span><span>
</span><span style="color:#7f8989;">// useful for debugging purposes as well
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'green:3.14:teal::brown:oh!:blue'</span><span>.</span><span style="color:#b39f04;">match</span><span>(</span><span style="color:#c49a39;">/:</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#c49a39;">:/</span><span style="color:#72ab00;">g</span><span>)
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#d07711;">':3.14:'</span><span>, </span><span style="color:#d07711;">'::'</span><span>, </span><span style="color:#d07711;">':oh!:'</span><span>]
</span></code></pre>
<ul>
<li><code>matchAll()</code> method</li>
</ul>
<pre data-lang="ts" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#7f8989;">// same as: match(/so*n/g)
</span><span style="color:#72ab00;">> </span><span style="color:#a2a001;">Array</span><span>.</span><span style="color:#c23f31;">from</span><span>(</span><span style="color:#d07711;">'song too soon snatch'</span><span>.</span><span style="color:#c23f31;">matchAll</span><span>(</span><span style="color:#c49a39;">/so</span><span style="color:#72ab00;">*</span><span style="color:#c49a39;">n/</span><span style="color:#72ab00;">g</span><span>), </span><span style="color:#5597d6;">m </span><span style="color:#668f14;">=> </span><span style="color:#5597d6;">m</span><span>[</span><span style="color:#b3933a;">0</span><span>])
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#d07711;">'son'</span><span>, </span><span style="color:#d07711;">'soon'</span><span>, </span><span style="color:#d07711;">'sn'</span><span>]
</span><span style="color:#7f8989;">// get the starting index for each match
</span><span style="color:#72ab00;">> </span><span style="color:#a2a001;">Array</span><span>.</span><span style="color:#c23f31;">from</span><span>(</span><span style="color:#d07711;">'song too soon snatch'</span><span>.</span><span style="color:#c23f31;">matchAll</span><span>(</span><span style="color:#c49a39;">/so</span><span style="color:#72ab00;">*</span><span style="color:#c49a39;">n/</span><span style="color:#72ab00;">g</span><span>), </span><span style="color:#5597d6;">m </span><span style="color:#668f14;">=> </span><span style="color:#5597d6;">m</span><span>.</span><span style="color:#a2a001;">index</span><span>)
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#b3933a;">0</span><span>, </span><span style="color:#b3933a;">9</span><span>, </span><span style="color:#b3933a;">14</span><span>]
</span><span>
</span><span style="color:#7f8989;">// get only the capture group portions as an array for each match
</span><span style="color:#72ab00;">> </span><span style="color:#a2a001;">Array</span><span>.</span><span style="color:#c23f31;">from</span><span>(</span><span style="color:#d07711;">'2023/04,1986/Mar,'</span><span>.</span><span style="color:#c23f31;">matchAll</span><span>(</span><span style="color:#c49a39;">/(</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#c49a39;">)</span><span style="color:#108f3d;">\/</span><span style="color:#c49a39;">(</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#c49a39;">),/</span><span style="color:#72ab00;">g</span><span>), </span><span style="color:#5597d6;">m </span><span style="color:#668f14;">=> </span><span style="color:#5597d6;">m</span><span>.</span><span style="color:#b39f04;">slice</span><span>(</span><span style="color:#b3933a;">1</span><span>))
</span><span style="color:#72ab00;">< </span><span>(</span><span style="color:#b3933a;">2</span><span>) [</span><span style="color:#a2a001;">Array</span><span>(</span><span style="color:#b3933a;">2</span><span>), </span><span style="color:#a2a001;">Array</span><span>(</span><span style="color:#b3933a;">2</span><span>)]
</span><span> </span><span style="color:#b3933a;">0</span><span>: (</span><span style="color:#b3933a;">2</span><span>) [</span><span style="color:#d07711;">'2023'</span><span>, </span><span style="color:#d07711;">'04'</span><span>]
</span><span> </span><span style="color:#b3933a;">1</span><span>: (</span><span style="color:#b3933a;">2</span><span>) [</span><span style="color:#d07711;">'1986'</span><span>, </span><span style="color:#d07711;">'Mar'</span><span>]
</span><span> </span><span style="color:#c23f31;">length</span><span>: </span><span style="color:#b3933a;">2
</span><span> [[</span><span style="color:#5597d6;">Prototype</span><span>]]: </span><span style="color:#a2a001;">Array</span><span>(</span><span style="color:#b3933a;">0</span><span>)
</span></code></pre>
<ul>
<li>function/dictionary in the replacement section</li>
</ul>
<pre data-lang="ts" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#72ab00;">> </span><span style="color:#668f14;">function </span><span style="color:#c23f31;">titleCase</span><span>(</span><span style="color:#5597d6;">m</span><span>, </span><span style="color:#5597d6;">g1</span><span>, </span><span style="color:#5597d6;">g2</span><span>) {
</span><span> </span><span style="color:#72ab00;">return </span><span style="color:#5597d6;">g1</span><span>.</span><span style="color:#b39f04;">toUpperCase</span><span>() </span><span style="color:#72ab00;">+ </span><span style="color:#5597d6;">g2</span><span>.</span><span style="color:#b39f04;">toLowerCase</span><span>()
</span><span> }
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'aBc ac ADC aBbBC'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/(a)(</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#c49a39;">c)/</span><span style="color:#72ab00;">ig</span><span>, </span><span style="color:#5597d6;">titleCase</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'Abc Ac Adc Abbbc'
</span><span>
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'1 42 317'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">g</span><span>, </span><span style="color:#5597d6;">m </span><span style="color:#668f14;">=> </span><span style="color:#5597d6;">m</span><span style="color:#72ab00;">*</span><span style="color:#b3933a;">2</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'2 84 634'
</span><span>
</span><span style="color:#72ab00;">> </span><span style="color:#668f14;">let </span><span style="color:#5597d6;">swap </span><span style="color:#72ab00;">= </span><span>{ </span><span style="color:#d07711;">'cat'</span><span>: </span><span style="color:#d07711;">'tiger'</span><span>, </span><span style="color:#d07711;">'tiger'</span><span>: </span><span style="color:#d07711;">'cat' </span><span>}
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'cat tiger dog tiger cat'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/cat</span><span style="color:#72ab00;">|</span><span style="color:#c49a39;">tiger/</span><span style="color:#72ab00;">g</span><span>, </span><span style="color:#5597d6;">k </span><span style="color:#668f14;">=> </span><span style="color:#5597d6;">swap</span><span>[</span><span style="color:#5597d6;">k</span><span>])
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'tiger cat dog cat tiger'
</span></code></pre>
<ul>
<li><code>split()</code> method</li>
</ul>
<pre data-lang="ts" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#7f8989;">// split based on one or more digit characters
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'Sample123string42with777numbers'</span><span>.</span><span style="color:#b39f04;">split</span><span>(</span><span style="color:#c49a39;">/</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">/</span><span>)
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#d07711;">'Sample'</span><span>, </span><span style="color:#d07711;">'string'</span><span>, </span><span style="color:#d07711;">'with'</span><span>, </span><span style="color:#d07711;">'numbers'</span><span>]
</span><span style="color:#7f8989;">// include the portion that caused the split as well
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'Sample123string42with777numbers'</span><span>.</span><span style="color:#b39f04;">split</span><span>(</span><span style="color:#c49a39;">/(</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">)/</span><span>)
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#d07711;">'Sample'</span><span>, </span><span style="color:#d07711;">'123'</span><span>, </span><span style="color:#d07711;">'string'</span><span>, </span><span style="color:#d07711;">'42'</span><span>, </span><span style="color:#d07711;">'with'</span><span>, </span><span style="color:#d07711;">'777'</span><span>, </span><span style="color:#d07711;">'numbers'</span><span>]
</span><span>
</span><span style="color:#7f8989;">// split based on digit or whitespace characters
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'**1</span><span style="color:#aeb52b;">\f</span><span style="color:#d07711;">2</span><span style="color:#aeb52b;">\n</span><span style="color:#d07711;">3star</span><span style="color:#aeb52b;">\t</span><span style="color:#d07711;">7 77</span><span style="color:#aeb52b;">\r</span><span style="color:#d07711;">**'</span><span>.</span><span style="color:#b39f04;">split</span><span>(</span><span style="color:#c49a39;">/</span><span style="color:#aeb52b;">[</span><span style="color:#b3933a;">\d\s</span><span style="color:#aeb52b;">]</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">/</span><span>)
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#d07711;">'**'</span><span>, </span><span style="color:#d07711;">'star'</span><span>, </span><span style="color:#d07711;">'**'</span><span>]
</span><span>
</span><span style="color:#7f8989;">// use non-capturing group if capturing is not needed
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'123handed42handy777handful500'</span><span>.</span><span style="color:#b39f04;">split</span><span>(</span><span style="color:#c49a39;">/hand(?:y</span><span style="color:#72ab00;">|</span><span style="color:#c49a39;">ful)</span><span style="color:#72ab00;">?</span><span style="color:#c49a39;">/</span><span>)
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#d07711;">'123'</span><span>, </span><span style="color:#d07711;">'ed42'</span><span>, </span><span style="color:#d07711;">'777'</span><span>, </span><span style="color:#d07711;">'500'</span><span>]
</span></code></pre>
<ul>
<li>backreferencing with normal/non-capturing/named capture groups</li>
</ul>
<pre data-lang="ts" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#7f8989;">// remove any number of consecutive duplicate words separated by space
</span><span style="color:#7f8989;">// use \W+ instead of space to cover cases like 'a;a<-;a'
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'aa a a a 42 f_1 f_1 f_13.14'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">\b</span><span style="color:#c49a39;">(</span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">)( </span><span style="color:#72ab00;">\1</span><span style="color:#c49a39;">)</span><span style="color:#72ab00;">+\b</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">g</span><span>, </span><span style="color:#d07711;">'$1'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'aa a 42 f_1 f_13.14'
</span><span>
</span><span style="color:#7f8989;">// add something around the entire matched portion
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'52 apples and 31 mangoes'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">g</span><span>, </span><span style="color:#d07711;">'($&)'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'(52) apples and (31) mangoes'
</span><span>
</span><span style="color:#7f8989;">// duplicate the first field and add it as the last field
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'fork,42,nice,3.14'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/,</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">/</span><span>, </span><span style="color:#d07711;">'$&,$`'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'fork,42,nice,3.14,fork'
</span><span>
</span><span style="color:#7f8989;">// use non-capturing groups when backreferencing isn't needed
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'1,2,3,4,5,6,7'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">^</span><span style="color:#c49a39;">((?:</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">,]</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">,)</span><span style="color:#72ab00;">{3}</span><span style="color:#c49a39;">)(</span><span style="color:#aeb52b;">[</span><span style="color:#72ab00;">^</span><span style="color:#aeb52b;">,]</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">)/</span><span>, </span><span style="color:#d07711;">'$1($2)'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'1,2,3,(4),5,6,7'
</span><span>
</span><span style="color:#7f8989;">// named capture groups, same as: replace(/(\w+),(\w+)/g, '$2,$1')
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'good,bad 42,24 x,y'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/(?<</span><span style="color:#acb3c2;">fw</span><span style="color:#c49a39;">></span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">),(?<</span><span style="color:#acb3c2;">sw</span><span style="color:#c49a39;">></span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">)/</span><span style="color:#72ab00;">g</span><span>, </span><span style="color:#d07711;">'$<sw>,$<fw>'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'bad,good 24,42 y,x'
</span></code></pre>
<ul>
<li>examples for lookarounds</li>
</ul>
<pre data-lang="ts" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#7f8989;">// change 'cat' only if it is not followed by a digit character
</span><span style="color:#7f8989;">// note that the end of string satisfies the given assertion
</span><span style="color:#7f8989;">// 'catcat' has two matches as the assertion doesn't consume characters
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'hey cats! cat42 cat_5 catcat'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/cat(?!</span><span style="color:#aeb52b;">\d</span><span style="color:#c49a39;">)/</span><span style="color:#72ab00;">g</span><span>, </span><span style="color:#d07711;">'dog'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'hey dogs! cat42 dog_5 dogdog'
</span><span>
</span><span style="color:#7f8989;">// change whole word only if it is not preceded by : or --
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">':cart apple --rest ;tea'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/(?<!:</span><span style="color:#72ab00;">|</span><span style="color:#c49a39;">--)</span><span style="color:#72ab00;">\b</span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">g</span><span>, </span><span style="color:#d07711;">'X'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">':cart X --rest ;X'
</span><span>
</span><span style="color:#7f8989;">// extract digits only if it is preceded by - and followed by ; or :
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'42 apple-5, fig3; x-83, y-20: f12'</span><span>.</span><span style="color:#b39f04;">match</span><span>(</span><span style="color:#c49a39;">/(?<=-)</span><span style="color:#aeb52b;">\d</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">(?=</span><span style="color:#aeb52b;">[;:]</span><span style="color:#c49a39;">)/</span><span style="color:#72ab00;">g</span><span>)
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#d07711;">'20'</span><span>]
</span><span>
</span><span style="color:#7f8989;">// words containing all lowercase vowels in any order
</span><span style="color:#72ab00;">> </span><span style="color:#668f14;">let </span><span style="color:#5597d6;">words </span><span style="color:#72ab00;">= </span><span>[</span><span style="color:#d07711;">'sequoia'</span><span>, </span><span style="color:#d07711;">'questionable'</span><span>, </span><span style="color:#d07711;">'exhibit'</span><span>, </span><span style="color:#d07711;">'equation'</span><span>]
</span><span style="color:#72ab00;">> </span><span style="color:#5597d6;">words</span><span>.</span><span style="color:#c23f31;">filter</span><span>(</span><span style="color:#5597d6;">w </span><span style="color:#668f14;">=> </span><span style="color:#c49a39;">/(?=</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*</span><span style="color:#c49a39;">a)(?=</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*</span><span style="color:#c49a39;">e)(?=</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*</span><span style="color:#c49a39;">i)(?=</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*</span><span style="color:#c49a39;">o)</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*</span><span style="color:#c49a39;">u/</span><span>.</span><span style="color:#b39f04;">test</span><span>(</span><span style="color:#5597d6;">w</span><span>))
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#d07711;">'sequoia'</span><span>, </span><span style="color:#d07711;">'questionable'</span><span>, </span><span style="color:#d07711;">'equation'</span><span>]
</span><span>
</span><span style="color:#7f8989;">// replace only the third occurrence of 'cat'
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'cat scatter cater scat'</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#c49a39;">/(?<=(cat</span><span style="color:#aeb52b;">.</span><span style="color:#72ab00;">*?</span><span style="color:#c49a39;">)</span><span style="color:#72ab00;">{2}</span><span style="color:#c49a39;">)cat/</span><span>, </span><span style="color:#d07711;">'X'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'cat scatter Xer scat'
</span><span>
</span><span style="color:#7f8989;">// match if 'do' is not there between 'at' and 'par'
</span><span style="color:#72ab00;">> </span><span style="color:#c49a39;">/at((?!do)</span><span style="color:#aeb52b;">.</span><span style="color:#c49a39;">)</span><span style="color:#72ab00;">*</span><span style="color:#c49a39;">par/</span><span>.</span><span style="color:#b39f04;">test</span><span>(</span><span style="color:#d07711;">'fox,cat,dog,parrot'</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#b3933a;">false
</span></code></pre>
<ul>
<li><code>u</code> and <code>v</code> flags</li>
</ul>
<pre data-lang="ts" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#7f8989;">// extract all consecutive letters, use \P{L} to invert the set
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'fox:αλεπού,eagle:αετός'</span><span>.</span><span style="color:#b39f04;">match</span><span>(</span><span style="color:#c49a39;">/</span><span style="color:#108f3d;">\p</span><span style="color:#c49a39;">{L}</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">gu</span><span>)
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#d07711;">'fox'</span><span>, </span><span style="color:#d07711;">'αλεπού'</span><span>, </span><span style="color:#d07711;">'eagle'</span><span>, </span><span style="color:#d07711;">'αετός'</span><span>]
</span><span>
</span><span style="color:#7f8989;">// extract all consecutive Greek letters
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'fox:αλεπού,eagle:αετός'</span><span>.</span><span style="color:#b39f04;">match</span><span>(</span><span style="color:#c49a39;">/</span><span style="color:#108f3d;">\p</span><span style="color:#c49a39;">{sc=Greek}</span><span style="color:#72ab00;">+</span><span style="color:#c49a39;">/</span><span style="color:#72ab00;">gu</span><span>)
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#d07711;">'αλεπού'</span><span>, </span><span style="color:#d07711;">'αετός'</span><span>]
</span><span>
</span><span style="color:#7f8989;">// extract whole words not surrounded by punctuation marks
</span><span style="color:#72ab00;">> </span><span style="color:#d07711;">'tie. ink east;'</span><span>.</span><span style="color:#b39f04;">match</span><span>(</span><span style="color:#c49a39;">/(?<!</span><span style="color:#108f3d;">\p</span><span style="color:#c49a39;">{P})</span><span style="color:#72ab00;">\b</span><span style="color:#aeb52b;">\w</span><span style="color:#72ab00;">+\b</span><span style="color:#c49a39;">(?!</span><span style="color:#108f3d;">\p</span><span style="color:#c49a39;">{P})/</span><span style="color:#72ab00;">gu</span><span>)
</span><span style="color:#72ab00;">< </span><span>[</span><span style="color:#d07711;">'ink'</span><span>]
</span><span>
</span><span style="color:#7f8989;">// remove all punctuation characters except . ! and ?
</span><span style="color:#72ab00;">> </span><span style="color:#668f14;">let </span><span style="color:#5597d6;">para </span><span style="color:#72ab00;">= </span><span style="color:#d07711;">'"Hi", there! How *are* you? All fine here.'
</span><span style="color:#72ab00;">> </span><span style="color:#5597d6;">para</span><span>.</span><span style="color:#b39f04;">replace</span><span>(</span><span style="color:#72ab00;">/</span><span>[\</span><span style="color:#5597d6;">p</span><span>{</span><span style="color:#5597d6;">P</span><span>}</span><span style="color:#72ab00;">--</span><span>[.</span><span style="color:#72ab00;">!?</span><span>]]</span><span style="color:#72ab00;">+/</span><span style="color:#5597d6;">gv</span><span>, </span><span style="color:#d07711;">''</span><span>)
</span><span style="color:#72ab00;">< </span><span style="color:#d07711;">'Hi there! How are you? All fine here.'
</span></code></pre>
<br>
<h2 id="debugging-and-visualization-tools">Debugging and Visualization tools<a class="zola-anchor" href="#debugging-and-visualization-tools" aria-label="Anchor link for: debugging-and-visualization-tools">🔗</a></h2>
<p>As your regexp gets complicated, it can get difficult to debug when you run into issues. Building your regexp step by step from scratch and testing against input strings will go a long way in correcting the problem. To aid in such a process, you could use <a href="https://news.ycombinator.com/item?id=20614847">various online regexp tools</a>.</p>
<p><a href="https://regex101.com/r/HSeO0z/1">regex101</a> is a popular site to test your regexp. You'll have to first choose the flavor as JavaScript. Then you can add your regexp, input strings, choose flags and an optional replacement string. Matching portions will be highlighted and explanation is offered in separate panes. There's also a quick reference and other features like link sharing, code generator, quiz, cheatsheet, etc.</p>
<p align="center"><img src="/images/books/regex101.png" alt="regex101 example" /></p>
<p>Another useful tool is <a href="https://jex.im/regulex/#!flags=&re=%5Cbpar(en%7Cro)%3Ft%5Cb">jex: regulex</a> which converts your regexp to a railroad diagram, thus providing a visual aid to understanding the pattern.</p>
<p align="center"><img src="/images/books/regulex.png" alt="regulex example" /></p>
<br>
<h2 id="understanding-javascript-regexp-book">Understanding JavaScript RegExp book<a class="zola-anchor" href="#understanding-javascript-regexp-book" aria-label="Anchor link for: understanding-javascript-regexp-book">🔗</a></h2>
<p>Visit my repo <a href="https://github.com/learnbyexample/learn_js_regexp">learn_js_regexp</a> for details about the book I wrote on JavaScript regular expressions. The ebook uses plenty of examples to explain the concepts from the basics and includes <a href="https://github.com/learnbyexample/learn_js_regexp/blob/master/Exercises.md">exercises</a> to test your understanding. The cheatsheet and examples presented in this post are based on the contents of this book.</p>
<p align="center"><img src="https://raw.githubusercontent.com/learnbyexample/learn_js_regexp/master/images/js_regexp_ls.png" width="640px" height="360px" alt="Understanding JavaScript RegExp cover image" loading="lazy" /></p>
Creating GUI Applications with wxPython - book review2019-05-13T00:00:00+00:002023-02-09T00:00:00+00:00https://learnbyexample.github.io/python-gui-book-review/<p align="center"><img src="/images/python_gui/GUI_example.jpg" alt="GUI example" /></p>
<p><em>Photo Credit: <a href="https://www.pexels.com/photo/apple-computer-desk-devices-326501/">Tranmautritam</a> on <a href="https://www.pexels.com/">Pexels</a></em></p>
<span id="continue-reading"></span><br>
<p>I've always wanted to create nice looking, useful GUI applications over the years. And I've given up most of the time as the programming seemed too difficult for me and GUI requires at least some level of design skills. I only managed to grit through one Android app for over a year as it was a dream game from school days and I had loads of free time having quit my job. At the end of it though, I had a spaghetti mess of several 1000+ lines programs and a strong aversion to Java and object oriented programming. Part of the reason is that I didn't try to learn in a formal way, just started from a tutorial closest to the game I wanted to do.</p>
<p>Several years later, here I am, trying my hand with GUI again. I have several small to medium scale apps in mind to implement and hopefully I'll avoid previous mistakes, especially feature creep. When I saw <a href="https://twitter.com/driscollis/status/1109106540160733184">this tweet from Mike Driscoll</a>, I took up the offer. I got a free book in exchange for reviewing <a href="https://www.blog.pythonlibrary.org/2019/05/08/creating-gui-applications-with-wxpython-now-available/">Creating GUI Applications with wxPython</a>. The book is currently on sale till May 15. Having to review has served as an extra incentive to read the book regularly, and so far I'm quite satisfied to have done so.</p>
<p align="center"><img src="/images/python_gui/wxPython_book_cover.png" alt="book cover" /></p>
<p>I hadn't heard of <a href="https://wxpython.org">wxPython</a> before this book. When it comes to GUI in Python, I knew about <code>tkinter</code> which comes by default with standard libary, <a href="https://kivy.org">Kivy</a>, <a href="https://www.pygame.org">Pygame</a> and <a href="https://pypi.org/project/PyQt5/">PyQt5</a>. This book starts with an introduction to <code>wxPython</code> and then dives into project-based approach. I've finished half the chapters so far, covering four project concepts:</p>
<ul>
<li>Image viewer</li>
<li>Database viewer and editor</li>
<li>Calculator</li>
<li>Archiver</li>
</ul>
<p align="center"><img src="/images/python_gui/calculator.png" alt="calculator" /></p>
<p>Rest of the chapters cover these topics:</p>
<ul>
<li>MP3 tag editor</li>
<li>Image application using NASA's API</li>
<li>PDF merger/splitter</li>
<li>File search</li>
<li>FTP application</li>
<li>XML editor</li>
<li>Distributing your application</li>
</ul>
<p>There are also a couple of appendix chapters.</p>
<p>As mentioned in book's introduction, you definitely need to be comfortable with Python classes before you start this book. The code used in the book is also available from <a href="https://github.com/driscollis/applications_with_wxpython">GitHub repo</a>, but I highly recommend to type them manually.</p>
<p>The project nature also means that after chapter 3, you could probably skip chapters you are not interested in. For example, I didn't pay too much attention to database chapters as I don't have much experience with databases. Each project is described and shown step by step. The projects could be run at different stages as well - playing around with the GUI at those points helps in mapping code-to-output, as well as to experiment different settings.</p>
<p>All in all, I would highly recommend this book for those wanting to start coding GUI applications in Python. And please do contact the author to let him know your feedback or if you have any clarifications. Happy learning :)</p>
Python for maths2019-03-22T00:00:00+00:002023-02-09T00:00:00+00:00https://learnbyexample.github.io/python-for-maths/<p align="center"><img src="/images/python_for_maths/gravitational_plot.png" alt="sample plot" /></p>
<p>The above image was generated using <code>matplotlib</code> courtesy code provided by <a href="https://github.com/doingmathwithpython/code/blob/master/chapter2/Chapter2.ipynb">Doing Math with Python</a> book.</p>
<span id="continue-reading"></span>
<p>Last month, I had an opportunity to conduct beginner Python workshop for maths department students in an arts and science college. It was a great experience and I had my first taste of how Python could be applied for mathematical problems. Presented here are bunch of useful links that I gathered as resources for the students. </p>
<h1 id="documentation-links">Documentation links<a class="zola-anchor" href="#documentation-links" aria-label="Anchor link for: documentation-links">🔗</a></h1>
<ul>
<li><a href="https://docs.python.org/3/">docs.python</a></li>
<li><a href="https://docs.scipy.org/doc/">numpy and scipy</a></li>
<li><a href="https://matplotlib.org/stable/api/index.html">matplotlib</a></li>
</ul>
<br>
<h1 id="books-and-courses">Books and courses<a class="zola-anchor" href="#books-and-courses" aria-label="Anchor link for: books-and-courses">🔗</a></h1>
<ul>
<li><a href="https://doingmathwithpython.github.io/">Doing Math with Python</a></li>
<li><a href="https://maths-with-python.readthedocs.io/en/latest/">Maths with Python</a></li>
<li><a href="https://github.com/drvinceknight/Python-Mathematics-Handbook">Doing mathematics with Python</a></li>
<li><a href="https://github.com/jrjohansson/scientific-python-lectures">Lectures on scientific computing with Python</a></li>
<li><a href="https://greenteapress.com/thinkdsp/html/index.html">Digital Signal Processing in Python</a></li>
<li><a href="https://www.coursera.org/learn/audio-signal-processing">Audio Signal Processing for Music Applications</a></li>
<li><a href="https://www.coursera.org/learn/what-is-a-proof">Mathematical Thinking in Computer Science</a></li>
</ul>
<br>
<h1 id="python-for-beginners">Python for beginners<a class="zola-anchor" href="#python-for-beginners" aria-label="Anchor link for: python-for-beginners">🔗</a></h1>
<ul>
<li><a href="https://automatetheboringstuff.com/">Automate the Boring Stuff with Python</a> — teaches you programming concepts and then shows how to automate everyday problems</li>
<li><a href="https://runestone.academy/ns/books/published/thinkcspy/index.html">How to Think Like a Computer Scientist: Interactive Edition</a> — inspired by Think Python</li>
<li><a href="https://thepythoncodingbook.com/">The Python Coding Book</a> — friendly, relaxed programming book for beginners</li>
<li><a href="https://gto76.github.io/python-cheatsheet/">Comprehensive Python cheatsheet</a></li>
<li><a href="https://www.pythontutor.com/visualize.html#mode=edit">Pythontutor: Visualize code execution</a> — also has example codes and ability to share sessions</li>
<li><a href="https://jvns.ca/blog/2019/06/23/a-few-debugging-resources/">What does debugging a program look like?</a></li>
<li><a href="https://ryanstutorials.net/problem-solving-skills/">Problem solving skills</a></li>
</ul>
<p><img src="/images/info.svg" alt="info" /> See my <a href="https://learnbyexample.github.io/py_resources/">comprehensive Python learning resources</a> for more.</p>
<br>
<h1 id="numpy-scipy-matplotlib">numpy, scipy, matplotlib<a class="zola-anchor" href="#numpy-scipy-matplotlib" aria-label="Anchor link for: numpy-scipy-matplotlib">🔗</a></h1>
<ul>
<li><a href="https://www.labri.fr/perso/nrougier/from-python-to-numpy/">From Python to Numpy</a></li>
<li><a href="https://nbviewer.org/github/vlad17/np-learn/blob/master/presentation.ipynb">Advanced Numpy Techniques</a></li>
<li><a href="https://github.com/donnemartin/data-science-ipython-notebooks">List of data science Python notebooks</a></li>
<li><a href="https://scipy-lectures.org/">Scipy Lecture Notes</a></li>
<li><a href="https://github.com/rougier/scientific-visualization-book">Scientific Visualization: Python + Matplotlib</a></li>
<li><a href="https://www.python-graph-gallery.com/">Collection of charts with Matplotlib, Seaborn, Plotly, etc</a></li>
<li><a href="https://ipgp.github.io/scientific_python_cheat_sheet/">Scientific Python Cheatsheet</a></li>
<li><a href="https://animatplot.readthedocs.io/en/stable/tutorial/getting_started.html">animatplot</a></li>
<li><a href="https://alimanfoo.github.io/2017/01/23/go-faster-python.html">benchmarking, profiling and optimising Python code</a> - includes discussion on numpy</li>
</ul>
<br>
<h1 id="more-resources">More resources<a class="zola-anchor" href="#more-resources" aria-label="Anchor link for: more-resources">🔗</a></h1>
<ul>
<li><a href="https://mathoverflow.net/questions/308797/what-programming-language-should-a-professional-mathematician-know">What programming language should a professional mathematician know?</a></li>
<li><a href="https://wiki.python.org/moin/BeginnersGuide/Mathematics">Python Beginners Guide for Mathematics</a></li>
<li><a href="https://github.com/jupyter/jupyter/wiki#a-gallery-of-interesting-jupyter-notebooks">Interesting Jupyter Notebooks on mathematics</a></li>
<li><a href="https://github.com/learnbyexample/curated_resources/blob/master/Education.md#maths">Maths curated resource links</a></li>
<li><a href="https://www.sagemath.org/">Sagemath</a></li>
<li><a href="https://octave.org/">GNU Octave</a></li>
</ul>
A short and satisfying bug hunt2019-03-06T00:00:00+00:002023-02-09T00:00:00+00:00https://learnbyexample.github.io/a-short-and-satisfying-bug-hunt/<h2 id="the-surprise">The surprise<a class="zola-anchor" href="#the-surprise" aria-label="Anchor link for: the-surprise">🔗</a></h2>
<p>So, a pleasant surprise awaited me last Sunday. As is my usual habit, I opened my <a href="https://github.com/learnbyexample">github</a> account after breakfast to see if I've got any sudden spurt in traffic. And as usual, things were normal. Except for the blue notification, which was rare. I hoped it wasn't a silly pull request and thankfully it was a <a href="https://github.com/learnbyexample/Command-line-text-processing/issues/24">new issue</a> that was opened.</p>
<span id="continue-reading"></span>
<p>I gave the issue a cursory glance and wrongly guessed it was probably some line ending issue (user was on Windows OS). As someone who has seen plenty of bugs in previous job, I wasn't ruling out anything though. I first cloned the repo so as to try to recreate the working environment without possible interference from my local working copy. As the user had provided detailed information while opening the issue, I was able to quickly replicate it. Sure enough, I was seeing the same problem. I only wondered why it wasn't brought to my attention before. Either past users chose not to or things weren't interesting enough to reach that far in the exercises.</p>
<h2 id="creating-minimal-failing-case">Creating minimal failing case<a class="zola-anchor" href="#creating-minimal-failing-case" aria-label="Anchor link for: creating-minimal-failing-case">🔗</a></h2>
<p>As I had written the <a href="https://github.com/learnbyexample/Command-line-text-processing/blob/master/exercises/GNU_grep/solve">solution checker script</a> about 2 years back, the script looked alien. Right from cloning the repo, I had to fight the urge to improve things. By the time I spotted the issue, all such fantasies were thrown out. Replaced by a todo note to <em>someday</em> write automated testing script to check that my script is indeed working properly for all the exercises.</p>
<p>To put it simply, the role of <code>solve</code> script is to check if the previous command executed by the user solves the current exercise question. To do so, the script gets the previous command from history and compares the output of that command and a reference solution present in the exercise directory. Sounds simple right? Yeah, I thought so too. I do remember testing few cases before I first published it and no one had submitted an issue so far. So, why was it failing now?</p>
<p>As mentioned before, I thought it could be some weird line ending issue. But that was effectively ruled out as it was failing for me as well on Linux. Still, I did check for funny characters with <code>cat -A</code>. Nope, no issues there.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> grep</span><span style="color:#5597d6;"> -o </span><span style="color:#d07711;">'^[^=]*'</span><span> sample.txt
</span><span style="color:#5597d6;">a[2]
</span><span style="color:#5597d6;">foo_bar
</span><span style="color:#5597d6;">appx_pi
</span><span style="color:#5597d6;">greeting
</span><span style="color:#5597d6;">food[4]
</span><span style="color:#5597d6;">b[0][1]
</span><span style="color:#5597d6;">$</span><span> source ../solve</span><span style="color:#5597d6;"> -s
</span><span style="color:#5597d6;">---------------------------------------------
</span><span style="color:#5597d6;">Mismatch</span><span> for question 1:
</span><span style="color:#5597d6;">Expected</span><span> output is:
</span><span style="color:#5597d6;">a[2]
</span><span style="color:#5597d6;">foo_bar
</span><span style="color:#5597d6;">appx_pi
</span><span style="color:#5597d6;">greeting
</span><span style="color:#5597d6;">food[4]
</span><span style="color:#5597d6;">b[0][1]
</span><span style="color:#5597d6;">---------------------------------------------
</span></code></pre>
<p>Expected output was same as output for submitted solution. So, why is the script failing? I remember passing the script through <a href="https://www.shellcheck.net/">shellcheck</a> but still checked it again. No progress. So, then I started by trying to debug the most likely culprit from terminal before trying to debug the whole script. Luckily, that turned out well.</p>
<pre data-lang="bash" style="background-color:#f5f5f5;color:#1f1f1f;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#5597d6;">$</span><span> cat sample.txt
</span><span style="color:#5597d6;">a[</span><span style="color:#b3933a;">2</span><span style="color:#5597d6;">]</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">'sample string'
</span><span style="color:#5597d6;">foo_bar</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">4232
</span><span style="color:#5597d6;">appx_pi</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">3.14
</span><span style="color:#5597d6;">greeting</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">"Hi there have a nice day"
</span><span style="color:#5597d6;">food[</span><span style="color:#b3933a;">4</span><span style="color:#5597d6;">]</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">"dosa"
</span><span style="color:#5597d6;">b[</span><span style="color:#b3933a;">0</span><span style="color:#5597d6;">][</span><span style="color:#b3933a;">1</span><span style="color:#5597d6;">]</span><span style="color:#72ab00;">=</span><span style="color:#d07711;">42
</span><span>
</span><span style="color:#5597d6;">$ </span><span style="color:#7f8989;"># say what??
</span><span style="color:#5597d6;">$ </span><span style="color:#72ab00;">[</span><span>[ $(</span><span style="color:#b39f04;">eval </span><span style="color:#d07711;">"command grep -o '^[^=]*' sample.txt"</span><span>) == \
</span><span>> $(</span><span style="color:#b39f04;">eval </span><span style="color:#d07711;">"command grep -o '^[^=]*' sample.txt"</span><span>) </span><span style="color:#72ab00;">]</span><span>] </span><span style="color:#72ab00;">|| </span><span style="color:#b39f04;">echo </span><span style="color:#d07711;">'Not fine'
</span><span style="color:#5597d6;">Not</span><span> fine
</span><span>
</span><span style="color:#5597d6;">$ </span><span style="color:#7f8989;"># after some attempts, I tried a command that won't have
</span><span style="color:#5597d6;">$ </span><span style="color:#7f8989;"># any [] characters in the output
</span><span style="color:#5597d6;">$ </span><span style="color:#7f8989;"># Eureka!
</span><span style="color:#5597d6;">$</span><span> [[ $(</span><span style="color:#b39f04;">eval </span><span style="color:#d07711;">"command grep 'bar' sample.txt"</span><span>) == \
</span><span style="color:#72ab00;">> </span><span>$(</span><span style="color:#b39f04;">eval </span><span style="color:#d07711;">"command grep 'bar' sample.txt"</span><span>) ]] </span><span style="color:#72ab00;">|| </span><span style="color:#b39f04;">echo </span><span style="color:#d07711;">'Not fine'
</span><span style="color:#5597d6;">$ </span><span style="color:#72ab00;">[</span><span>[ foo == foo </span><span style="color:#72ab00;">]</span><span>] </span><span style="color:#72ab00;">&& </span><span style="color:#b39f04;">echo </span><span style="color:#d07711;">'fine'
</span><span style="color:#5597d6;">fine
</span><span style="color:#5597d6;">$ </span><span style="color:#72ab00;">[</span><span>[ </span><span style="color:#d07711;">'a[5]'</span><span> == a[5</span><span style="color:#72ab00;">]</span><span> ]] </span><span style="color:#72ab00;">|| </span><span style="color:#b39f04;">echo </span><span style="color:#d07711;">'Not fine'
</span><span style="color:#5597d6;">Not</span><span> fine
</span><span style="color:#5597d6;">$ </span><span style="color:#72ab00;">[</span><span>[ </span><span style="color:#d07711;">'a[5]'</span><span> == </span><span style="color:#d07711;">'a[5]' </span><span style="color:#72ab00;">]</span><span>] </span><span style="color:#72ab00;">&& </span><span style="color:#b39f04;">echo </span><span style="color:#d07711;">'fine'
</span><span style="color:#5597d6;">fine
</span></code></pre>
<p>Having a minimal failing case from terminal was a relief. I tried <code>set -x</code> but that didn't light a bulb either. Finally, somehow I thought perhaps characters in the output was causing the issue and when <code>[]</code> characters were not present, the comparison worked as expected.</p>
<p>I did think quoting could be the issue, but dismissed it at first as both sides of comparison had the same command. Then my recent experience from reviewing <a href="https://www.packtpub.com/application-development/command-line-fundamentals">Command Line Fundamentals</a> book came in handy. I remembered that if quotes aren't used on RHS of comparison operator, it is treated as <code>glob</code> matching instead of string matching. Phew.</p>
<h2 id="tl-dr">TL;DR<a class="zola-anchor" href="#tl-dr" aria-label="Anchor link for: tl-dr">🔗</a></h2>
<p>Always <a href="https://unix.stackexchange.com/questions/131766/why-does-my-shell-script-choke-on-whitespace-or-other-special-characters">quote strings in bash</a> unless you have a very good reason for not using them.</p>
<p>After adding double quotes around the command substitution commands, the script worked as expected. I thanked the user for opening the issue. And then informed the author for cli fundamentals book as well.</p>