-
Notifications
You must be signed in to change notification settings - Fork 35
/
Copy pathindex.html
79 lines (79 loc) Β· 22.3 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<!doctype html><html lang=en><head><meta content="IE=edge" http-equiv=X-UA-Compatible><meta content="text/html; charset=utf-8" http-equiv=content-type><meta content="width=device-width,initial-scale=1.0,maximum-scale=1" name=viewport><title>Escaping madness to get literal field separators in awk</title><link href=https://learnbyexample.github.io/atom.xml rel=alternate title=RSS type=application/atom+xml><script src=https://cdnjs.cloudflare.com/ajax/libs/slideout/1.0.1/slideout.min.js></script><link href=https://learnbyexample.github.io/site.css rel=stylesheet><meta content="Escaping madness to get literal field separators in awk" property=og:title><meta content=website property=og:type><meta content="A confounding tale of adding a fixed string field splitting feature" property=og:description><meta content=https://learnbyexample.github.io/escaping-madness-awk-literal-field-separator/ property=og:url><meta content=@learn_byexample property=twitter:site><link href=https://learnbyexample.github.io/favicon.svg rel=icon><link rel="shortcut icon" href=https://learnbyexample.github.io/favicon.png><body><div class=container><div class=mobile-navbar id=mobile-navbar><div class=mobile-header-logo><a class=logo href=/>learnbyexample</a></div><div class="mobile-navbar-icon icon-out"><span></span><span></span><span></span></div></div><nav class="mobile-menu slideout-menu slideout-menu-left" id=mobile-menu><ul class=mobile-menu-list><li class=mobile-menu-item><a href=https://learnbyexample.github.io/books> Books </a><li class=mobile-menu-item><a href=https://learnbyexample.github.io/mini> Mini </a><li class=mobile-menu-item><a href=https://learnbyexample.github.io/tips> Tips </a><li class=mobile-menu-item><a href=https://learnbyexample.github.io/tags> Tags </a><li class=mobile-menu-item><a href=https://learnbyexample.github.io/about> About </a></ul></nav><header id=header><div class=logo><a href=https://learnbyexample.github.io>learnbyexample</a></div><nav class=menu><ul><li><a href=https://learnbyexample.github.io/books> Books </a><li><a href=https://learnbyexample.github.io/mini> Mini </a><li><a href=https://learnbyexample.github.io/tips> Tips </a><li><a href=https://learnbyexample.github.io/tags> Tags </a><li><a href=https://learnbyexample.github.io/about> About </a></ul></nav></header><main><div class=content id=mobile-panel><div class=post-toc id=post-toc><h2 class=post-toc-title>Contents</h2><div class="post-toc-content always-active"><nav id=TableOfContents><ul><li><a class=toc-link href=https://learnbyexample.github.io/escaping-madness-awk-literal-field-separator/#how-many-escapes-for-a-single-backslash>How many escapes for a single backslash?</a><li><a class=toc-link href=https://learnbyexample.github.io/escaping-madness-awk-literal-field-separator/#using-awk-to-generate-an-escaped-string>Using awk to generate an escaped string</a> <ul><li><a class=toc-link href=https://learnbyexample.github.io/escaping-madness-awk-literal-field-separator/#case-1-backslash-madness>Case 1: backslash madness</a><li><a class=toc-link href=https://learnbyexample.github.io/escaping-madness-awk-literal-field-separator/#case-2-character-class>Case 2: character class</a><li><a class=toc-link href=https://learnbyexample.github.io/escaping-madness-awk-literal-field-separator/#case-3-codepoint-to-represent-backslash>Case 3: codepoint to represent backslash</a></ul><li><a class=toc-link href=https://learnbyexample.github.io/escaping-madness-awk-literal-field-separator/#sanity-check>Sanity check</a></ul></nav></div></div><article class=post><header class=post__header><h1 class=post__title><a href=https://learnbyexample.github.io/escaping-madness-awk-literal-field-separator/>Escaping madness to get literal field separators in awk</a></h1><div class=post__meta><span class=post__time>2021-07-02</span></div></header><div class=post-content><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>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>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 aria-label="Anchor link for: how-many-escapes-for-a-single-backslash" class=zola-anchor href=#how-many-escapes-for-a-single-backslash>π</a></h2><p>For reference, these are the versions I have on my machine:<pre class=language-bash data-lang=bash style=background-color:#f5f5f5;color:#1f1f1f;><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.<pre class=language-bash data-lang=bash style=background-color:#f5f5f5;color:#1f1f1f;><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><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.<pre class=language-bash data-lang=bash style=background-color:#f5f5f5;color:#1f1f1f;><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><strong>Conclusion</strong>: You need <code>\x5c\x5c</code> for every backslash.<pre class=language-bash data-lang=bash style=background-color:#f5f5f5;color:#1f1f1f;><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 aria-label="Anchor link for: using-awk-to-generate-an-escaped-string" class=zola-anchor href=#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>:<pre class=language-bash data-lang=bash style=background-color:#f5f5f5;color:#1f1f1f;><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.<h3 id=case-1-backslash-madness>Case 1: backslash madness<a aria-label="Anchor link for: case-1-backslash-madness" class=zola-anchor href=#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>Ready for the solution? I'm not even going to try explaining this, found it by experimenting.<pre class=language-bash data-lang=bash style=background-color:#f5f5f5;color:#1f1f1f;><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 alt=info src=/images/info.svg> <img alt=warning src=/images/warning.svg> <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.<h3 id=case-2-character-class>Case 2: character class<a aria-label="Anchor link for: case-2-character-class" class=zola-anchor href=#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.<pre class=language-bash data-lang=bash style=background-color:#f5f5f5;color:#1f1f1f;><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 aria-label="Anchor link for: case-3-codepoint-to-represent-backslash" class=zola-anchor href=#case-3-codepoint-to-represent-backslash>π</a></h3><p>Finally, my preferred solutions that uses codepoint instead of escaping backslashes.<pre class=language-bash data-lang=bash style=background-color:#f5f5f5;color:#1f1f1f;><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 aria-label="Anchor link for: sanity-check" class=zola-anchor href=#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.</div><div class=post-footer><div class=post-tags><a href=https://learnbyexample.github.io/tags/gnu-awk/>#gnu-awk</a><a href=https://learnbyexample.github.io/tags/mawk/>#mawk</a><a href=https://learnbyexample.github.io/tags/regular-expressions/>#regular-expressions</a><a href=https://learnbyexample.github.io/tags/command-line/>#command-line</a></div><hr color=#e6e6e6><div class=post-nav><p><a class=previous href=https://learnbyexample.github.io/practice-python-projects-book-announcement/>β Practice Python Projects book announcement</a><br><p><a class=next href=https://learnbyexample.github.io/gnu-bre-ere-cheatsheet/>GNU BRE/ERE cheatsheet and differences between grep, sed and awk β</a><br></div><hr color=#e6e6e6><p>π° Use <a href=https://learnbyexample.github.io/atom.xml>this link</a> for the Atom feed. <br> β
Follow me on <a href=https://twitter.com/learn_byexample>Twitter</a>, <a href=https://github.com/learnbyexample>GitHub</a> and <a href=https://www.youtube.com/c/learnbyexample42>Youtube</a> for interesting tech nuggets. <br> π§ Subscribe to <a href=https://learnbyexample.gumroad.com/l/learnbyexample-weekly>learnbyexample weekly</a> for programming resources, tips, tools, free ebooks and more (free newsletter, delivered every Friday).<hr color=#e6e6e6></div></article></div></main></div><script src=https://learnbyexample.github.io/even.js></script>