<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>MarcArea.com Blog</title><link>https://marcarea.com/weblog/</link><description>Les derniers posts sur MarcArea.com Blog</description><atom:link href="https://marcarea.com/weblog/feed/atom/posts" rel="self"/><language>fr</language><lastBuildDate>Tue, 08 Sep 2026 15:26:07 +0000</lastBuildDate><item><title>From BPF to Wireshark: libpcap and wireshark-chmodbpf on macOS</title><link>https://marcarea.com/weblog/2026/09/08/from-bpf-to-wireshark-libpcap-and-wireshark-chmodbpf-on-macos</link><description>&lt;p&gt;On macOS, &lt;a href="https://www.wireshark.org/download.html"&gt;Wireshark&lt;/a&gt; uses libpcap to capture network packets, and libpcap uses BPF:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
        Network interface (en0)
                  │
         ┌──────────────────┐
         │   macOS kernel   │
         │ (BPF receives a  │
         │ copy of packets) │
         └─────────┬────────┘
                   │
    ┌──────────────┴──────────────┐
    │                             │
  normal                      /dev/bpf0
 delivery                     /dev/bpf1
(Safari, Mail                 /dev/bpf*
other apps)                       │
                               libpcap
                                  │
                              Wireshark
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="bpf"&gt;BPF&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Berkeley_Packet_Filter#cite_note-bpf93-1"&gt;BPF (Berkeley Packet Filter)&lt;/a&gt; is the packet-capture facility built into the BSD layer of the macOS kernel.&lt;/p&gt;

&lt;p&gt;It has two main parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/bsd/net/bpf.c"&gt;a tap mechanism&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;code that intercepts frames at the link layer&lt;/li&gt;
&lt;li&gt;this allows packets to get duplicated toward any attached BPF session as they pass through the network stack&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/bsd/net/bpf_filter.c"&gt;a filter engine&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a bytecode virtual machine that evaluates a filter program against each packet to decide whether to keep it or throw it away&lt;/li&gt;
&lt;li&gt;the filter program is supplied by the capture tool&lt;/li&gt;
&lt;li&gt;filtering happens in-kernel before anything gets copied to userspace, rather than copying everything and filtering afterward&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;BPF provides pseudo-devices such as &lt;code&gt;/dev/bpf0&lt;/code&gt;, &lt;code&gt;/dev/bpf1&lt;/code&gt;, etc. through which user-space programs access BPF packet capture and filtering.&lt;/p&gt;

&lt;p&gt;On macOS, there is an upper bound on the number of BPF pseudo-devices (&lt;code&gt;sysctl debug.bpf_maxdevices&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;BPF is the mechanism. &lt;code&gt;/dev/bpf*&lt;/code&gt; is the file-like door into it.&lt;/p&gt;

&lt;p&gt;When a program calls &lt;code&gt;open()&lt;/code&gt; on an available &lt;code&gt;/dev/bpf*&lt;/code&gt; pseudo-device, it obtains a BPF descriptor. It then attaches that descriptor to a specific network interface (&lt;code&gt;en0&lt;/code&gt;, &lt;code&gt;en1&lt;/code&gt;, etc.) and configures its capture parameters and filter.&lt;/p&gt;

&lt;p&gt;Once attached, the BPF descriptor receives copies of raw frames tapped from that interface and buffers them in the kernel until the program calls &lt;code&gt;read()&lt;/code&gt; on its file descriptor to drain them.&lt;/p&gt;

&lt;p&gt;This is what makes low-level packet sniffing possible, since normal sockets don't expose that level of detail.&lt;/p&gt;

&lt;h2 id="libpcap"&gt;libpcap and packet-filter expressions&lt;/h2&gt;

&lt;p&gt;Wireshark uses libpcap to access the BPF capture mechanism and to compile capture-filter expressions into BPF filter programs.&lt;/p&gt;

&lt;p&gt;You can see libpcap packet-filter expressions in &lt;a href="https://www.tcpdump.org/manpages/pcap-filter.7.html"&gt;&lt;code&gt;man pcap-filter&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example: &lt;code&gt;tcp port 443&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;libpcap compiles that expression into BPF bytecode.&lt;/p&gt;

&lt;h2 id="macports-wireshark-chmodbpf"&gt;MacPorts wireshark-chmodbpf&lt;/h2&gt;

&lt;p&gt;On macOS, &lt;code&gt;/dev/bpf*&lt;/code&gt; files are owned by root. Normal users can't capture packets without &lt;code&gt;sudo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ports.macports.org/port/wireshark-chmodbpf/details/"&gt;wireshark-chmodbpf&lt;/a&gt; is a permission helper for Wireshark on macOS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it creates a group called &lt;code&gt;access_bpf&lt;/code&gt; and instructs users to add their account to that group&lt;/li&gt;
&lt;li&gt;it generates a &lt;code&gt;launchd&lt;/code&gt; startup item:
&lt;ul&gt;
&lt;li&gt;at &lt;code&gt;/Library/LaunchDaemons/org.macports.wireshark.ChmodBPF.plist&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;set to run automatically as root every time the Mac boots&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;that daemon runs a &lt;em&gt;workaround&lt;/em&gt; script (&lt;a href="https://github.com/macports/macports-ports/blob/ae19634487ad0ec77c02865254a1164c7788e6c4/net/wireshark-chmodbpf/files/patch-wireshark-chmodbpf.diff"&gt;&lt;code&gt;/opt/local/sbin/wireshark-chmodbpf&lt;/code&gt;&lt;/a&gt;) that:
&lt;ol&gt;
&lt;li&gt;pre-creates up to 256 BPF pseudo-devices (or fewer if &lt;code&gt;debug.bpf_maxdevices&lt;/code&gt; is lower): this is because macOS can create BPF devices on demand, so a device created later would otherwise not receive the desired permissions&lt;/li&gt;
&lt;li&gt;allows members of the &lt;code&gt;access_bpf&lt;/code&gt; group to access the BPF pseudo-devices&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It has to run at every single boot because &lt;code&gt;/dev&lt;/code&gt; on macOS is rebuilt from scratch each startup.&lt;/p&gt;</description><pubDate>Tue, 08 Sep 2026 15:26:07 +0000</pubDate><guid>https://marcarea.com/weblog/2026/09/08/from-bpf-to-wireshark-libpcap-and-wireshark-chmodbpf-on-macos</guid></item><item><title>Notes on configuring MikroTik switches</title><link>https://marcarea.com/weblog/2026/08/16/notes-on-configuring-mikrotik-switches</link><description>&lt;p&gt;I'm using a MikroTik manageable Ethernet switch (the &lt;a href="https://mikrotik.com/product/crs304_4xg_in#product_specification"&gt;CRS304-4XG-IN&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://manual.mikrotik.com/docs/introduction"&gt;RouterOS&lt;/a&gt; is MikroTik's operating system running on the switch. That's a bit overkill for a switch, if you ask me, but &lt;a href="https://forum.mikrotik.com/t/is-there-a-swos-version-compatible-with-crs304-4xg-in/179925/15"&gt;SwOS doesn't work with this one&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://manual.mikrotik.com/docs/management-tools/webfig/"&gt;WebFig&lt;/a&gt; is the web-based UI for configuring RouterOS.&lt;/p&gt;

&lt;p&gt;RouterOS exposes implementation details that a networking textbook tends to abstract away.&lt;/p&gt;

&lt;p&gt;I know I'll forget these details in a couple of weeks, so I'm keeping these notes as a reference for later.&lt;/p&gt;

&lt;h2 id="a_simple_layer_2_network"&gt;A simple Layer-2 network&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;       Internet
          │
       Router
          │
        Port 1
          │
 ┌─────────────────┐
 │      CRS304     │
 │                 │
 │    L2 switch    │
 └─────────────────┘
   │      │      │
 Port2  Port3  Port4
   │      │      │
MacBook  NAS    PS5
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="the_bridge"&gt;The bridge&lt;/h2&gt;

&lt;p&gt;There is a "bridge" menu in WebFig.&lt;/p&gt;

&lt;p&gt;The bridge is a &lt;strong&gt;logical construct&lt;/strong&gt; (a software-defined interface) through which RouterOS groups Ethernet interfaces into a forwarding domain:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;bridge (logical switch)
│
├── ether1   ← router
├── ether2   ← MacBook
├── ether3   ← NAS
└── ether4   ← PS5
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;all four ports belong to the same bridge and to the same broadcast domain&lt;/li&gt;
&lt;li&gt;devices connected to them are part of the same network&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bridge is also where port switching, or at least part of it, is configured.&lt;/p&gt;

&lt;h2 id="switch_asic"&gt;Switch ASIC&lt;/h2&gt;

&lt;p&gt;A switch ASIC (&lt;a href="https://en.wikipedia.org/wiki/Application-specific_integrated_circuit"&gt;Application-Specific Integrated Circuit&lt;/a&gt;) is specialized hardware designed to perform switching-related operations.&lt;/p&gt;

&lt;p&gt;A switch could perform these operations using a general-purpose CPU, but that would be a terrible design for a high-speed switch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the CRS304 has four 10-Gigabit Ethernet ports&lt;/li&gt;
&lt;li&gt;at 10 Gbit/s, a port can carry up to 10 billion bits per second&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So instead, the CRS304 uses specialized switching hardware designed to operate at very high speed.&lt;/p&gt;

&lt;p&gt;For the CRS304:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Marvell = manufacturer (Marvell Technology, Inc.)&lt;/li&gt;
&lt;li&gt;Prestera = switch ASIC family&lt;/li&gt;
&lt;li&gt;98DX2528 = specific switch ASIC used by the CRS304&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MikroTik lists the 98DX2528 as both the switch chip and the CPU. It smells like a switch SoC (&lt;em&gt;System-on-Chip&lt;/em&gt;): a chip that combines a general-purpose CPU with dedicated Ethernet-switching hardware.&lt;/p&gt;

&lt;h2 id="hardware_offload"&gt;Hardware offload&lt;/h2&gt;

&lt;p&gt;RouterOS can program the switch ASIC to perform forwarding in hardware.&lt;/p&gt;

&lt;p&gt;This is called &lt;strong&gt;hardware offload&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the RouterOS interface, the &lt;code&gt;H&lt;/code&gt; flag indicates that hardware offloading is being used for the bridge port.&lt;/p&gt;

&lt;h2 id="webfig_8220bridge8221_and_8220switch8221_menus"&gt;WebFig: "bridge" and "switch" menus&lt;/h2&gt;

&lt;p&gt;WebFig has both a "Bridge" menu and a "Switch" menu.&lt;/p&gt;

&lt;p&gt;I don't fully understand why there are two menus. It looks like the switch menu is for chip-specific features that can't be expressed through the bridge configuration.&lt;/p&gt;

&lt;p&gt;Which menu owns a feature may depend on the chip…&lt;/p&gt;

&lt;p&gt;The day Ethernet switch manufacturers make a decent user interface will be the day pigs fly.&lt;/p&gt;

&lt;p&gt;Anyway, in my case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bridge UI = what should my Layer-2 network look like?&lt;/li&gt;
&lt;li&gt;Switch UI = what can the switch do?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="mental_model"&gt;Mental model&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;          RouterOS
             │
           Bridge
        configuration
             │
      hardware offload
             │
          Switch
       configuration
             │
       Marvell Prestera
          98DX2528
       switch ASIC chip
             │
   ┌─────────┼─────────┐
   ▼         ▼         ▼
ether1     ether2    ether3 …
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="port_based_mirroring"&gt;Port based mirroring&lt;/h2&gt;

&lt;p&gt;Port-mirroring is part of &lt;a href="https://manual.mikrotik.com/docs/bridging-and-switching/marvell-prestera-switch-chip-features/"&gt;Marvell Prestera switch chip features&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Example goal = everything the NAS receives, copied to the MacBook:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ether1 = Router
ether2 = MacBook   ← mirror target
ether3 = NAS       ← mirror source
ether4 = PS5
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In switch terminology:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ether3&lt;/code&gt; = source port&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ether2&lt;/code&gt; = mirror target = the port to which the copies of mirrored packets are sent&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mirror-ingress=yes&lt;/code&gt; = copy traffic entering &lt;code&gt;ether3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mirror-egress=yes&lt;/code&gt; = copy traffic leaving &lt;code&gt;ether3&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this example, only &lt;code&gt;mirror-egress&lt;/code&gt; is enabled because we want traffic going from the switch toward the NAS.&lt;/p&gt;

&lt;h3 id="via_ui"&gt;Via UI&lt;/h3&gt;

&lt;p&gt;Port mirroring is under the "Switch" menu:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;go to "Switch → Settings"
&lt;ul&gt;
&lt;li&gt;set "Mirror Target" to &lt;code&gt;ether2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;go to "Switch → Ports"
&lt;ul&gt;
&lt;li&gt;find &lt;code&gt;ether3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Mirror Egress → &lt;code&gt;yes&lt;/code&gt; (copy frames leaving &lt;code&gt;ether3&lt;/code&gt; toward the NAS)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id="via_cli"&gt;Via CLI&lt;/h3&gt;

&lt;p&gt;Inspect the current switch configuration:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;/interface ethernet switch print
/interface ethernet switch print detail
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Inspect the switch ports:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;/interface ethernet switch port print
/interface ethernet switch port print detail
/interface ethernet switch port print detail where name=ether3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Mirror the traffic the NAS receives (&lt;code&gt;ether3&lt;/code&gt;) on the MacBook (&lt;code&gt;ether2&lt;/code&gt;):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;/interface ethernet switch set [switch-name] mirror-target=ether2
/interface ethernet switch port set ether3 mirror-ingress=no mirror-egress=yes
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Revert the mirroring configuration:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;/interface ethernet switch set [switch-name] mirror-target=none
/interface ethernet switch port set ether3 mirror-ingress=no mirror-egress=no
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="reset_configuration"&gt;Reset the configuration&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://manual.mikrotik.com/hardware/crs304-4xg-in#reset-button"&gt;To reset the configuration&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unplug the device from power&lt;/li&gt;
&lt;li&gt;press and hold the Reset button&lt;/li&gt;
&lt;li&gt;plug the device&lt;/li&gt;
&lt;li&gt;release the Reset button when the USER (USR) LED starts flashing&lt;/li&gt;
&lt;/ul&gt;</description><pubDate>Sun, 16 Aug 2026 09:37:34 +0000</pubDate><guid>https://marcarea.com/weblog/2026/08/16/notes-on-configuring-mikrotik-switches</guid></item><item><title>Notes on the Internet</title><link>https://marcarea.com/weblog/2026/08/03/notes-on-the-internet</link><description>&lt;p&gt;It is remarkable that the Internet Protocol has remained so resilient for decades, given how rapidly communication technologies have evolved.&lt;/p&gt;

&lt;p&gt;Spending some time understanding the fundamental principles behind its operation is therefore worthwhile.&lt;/p&gt;

&lt;p&gt;That's why I'm publishing &lt;em&gt;the first version&lt;/em&gt; of my &lt;a href="/notes/internet/"&gt;notes on the Internet&lt;/a&gt; today.&lt;/p&gt;

&lt;p&gt;I chose &lt;a href="https://gaia.cs.umass.edu/kurose_ross/index.php"&gt;&lt;em&gt;Computer Networking: A Top-Down Approach&lt;/em&gt;&lt;/a&gt; (8th edition, 2021) as my primary source. It was a challenging read but I think I managed to get the most ouf of it.&lt;/p&gt;

&lt;p&gt;I redrew more than 100 figures as SVGs. In doing so, I used &lt;a href="/tweets/14457"&gt;Affinity instead of Illustrator&lt;/a&gt; for the first time.&lt;/p&gt;

&lt;p&gt;I also used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;icons from the &lt;a href="https://www.svgrepo.com/collection/solar-broken-line-icons/"&gt;&lt;em&gt;Solar Broken Line Icons Collection&lt;/em&gt;&lt;/a&gt; and the &lt;a href="https://www.svgrepo.com/collection/clarity-line-icons/"&gt;&lt;em&gt;Clarity Line Icons Collection&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;the &lt;a href="https://fonts.google.com/specimen/Roboto"&gt;Roboto&lt;/a&gt; typeface&lt;/li&gt;
&lt;li&gt;Anton Tsitsulin's &lt;a href="https://tsitsul.in/blog/coloropt/"&gt;12-color qualitative palette&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ChatGPT and Sonnet to clarify and correct parts of my notes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once again, it took way longer than I expected, and the process spanned several years. It took so long that a new edition of the book came out in the meantime :)&lt;/p&gt;

&lt;p&gt;I'll continue refining and expanding these notes over time.&lt;/p&gt;</description><pubDate>Mon, 03 Aug 2026 13:52:22 +0000</pubDate><guid>https://marcarea.com/weblog/2026/08/03/notes-on-the-internet</guid></item><item><title>State machines</title><link>https://marcarea.com/weblog/2026/07/03/state-machines</link><description>&lt;p&gt;State machines provide a way to break complex behavior into well-defined states and transitions.&lt;/p&gt;

&lt;h2 id="state_machine"&gt;State machine&lt;/h2&gt;

&lt;p&gt;A state machine is a model for describing system behavior over time.&lt;/p&gt;

&lt;p&gt;In a state machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a task is broken down into a series of &lt;em&gt;states&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;the system moves through these states along defined transitions&lt;/li&gt;
&lt;li&gt;the system transitions from one state to another based on inputs or other triggers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples of systems controlled by state machines include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;elevators&lt;/li&gt;
&lt;li&gt;traffic lights&lt;/li&gt;
&lt;li&gt;vending machines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;can only be in one state at any given time&lt;/li&gt;
&lt;li&gt;can perform different actions depending on inputs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="states_transitions_and_events"&gt;States, Transitions, and Events&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;state&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;describes the status of a system while it is waiting for a transition&lt;/li&gt;
&lt;li&gt;is one of a finite number of possible conditions&lt;/li&gt;
&lt;li&gt;for this reason, state machines are called &lt;strong&gt;finite state machines (FSMs)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A &lt;strong&gt;transition&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;is the action of moving from one state to another&lt;/li&gt;
&lt;li&gt;transitions are usually triggered by events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An &lt;strong&gt;event&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;is something that occurs, e.g., a button press or a timer expiring&lt;/li&gt;
&lt;li&gt;the same event might trigger a different transition depending on the current state of the state machine&lt;/li&gt;
&lt;li&gt;e.g., pressing the &amp;#8220;5&amp;#8221; button will make an elevator go down if it&amp;#8217;s on the tenth floor, or go up if it is on the first floor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Designing a state machine entails:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;determining all possible states&lt;/li&gt;
&lt;li&gt;planning the transitions between states&lt;/li&gt;
&lt;li&gt;identifying the events that trigger those transitions&lt;/li&gt;
&lt;/ul&gt;</description><pubDate>Fri, 03 Jul 2026 14:18:40 +0000</pubDate><guid>https://marcarea.com/weblog/2026/07/03/state-machines</guid></item><item><title>Quotes from Steve Jobs in Exile</title><link>https://marcarea.com/weblog/2026/06/01/quotes-from-steve-jobs-in-exile</link><description>&lt;p&gt;&lt;a href="https://stevejobs.tech/"&gt;&lt;em&gt;Steve Jobs in Exile&lt;/em&gt;&lt;/a&gt; by Geoffrey Cain chronicles the twelve years between Steve Jobs's departure from Apple in 1985 and his return in 1997.&lt;/p&gt;

&lt;p&gt;The book focuses on &lt;a href="/tweets/14407"&gt;Jobs's time at NeXT&lt;/a&gt; and argues that these years were crucial to his personal development. It was during this period that he evolved from a brilliant jerk into a more mature leader.&lt;/p&gt;

&lt;p&gt;Here are several quotes that caught my attention.&lt;/p&gt;

&lt;p&gt;Mach:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;On a muddy walk to lunch, Avie's fellow researchers joked about naming the microkernel "muck." A faculty member misheard it as "Mach" and the name stuck. (p.65)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A premonition of a short life:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;At the same time, Steve felt he was running out of time to make his mark, The feeling would surface in conversations with colleagues, when Steve told them about his intuition that he wouldn't live a long life. He couldn't explain where it came from, but at thirty-one, he believed his time was limited. (p.75)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;NeXT and Common Lisp:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;[Steve] wanted NeXT computers to crack open a field that most considered dead: artificial intelligence, The grand promises of the early '80s—computers that would diagnose cancer or argue legal cases—had crashed and burned in what they called "Al winter."&lt;/p&gt;
    &lt;p&gt;Yet when Steve visited labs at Stanford and Carnegie Mellon, he saw researchers hunched over $50,000 workstations, stubbornly teaching machines baby steps like understanding sentences or solving puzzles.&lt;/p&gt;
    &lt;p&gt;They wrote in Lisp, an exotic programming language that lets programmers manipulate ideas the way other languages manipulated numbers.&lt;/p&gt;
    &lt;p&gt;Steve made a decision that would have sounded insane to other executives: bundle Allegro Common Lisp—software that cost thousands per license—for free with every NeXT machine. While IBM and Sun treated Al tools as exotic luxuries, Steve was intent on putting them in university computer labs. (p.102)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The two laws of demoing:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;Then Steve turned to the demos. "For those of you that aren't software developers in the audience, I'd like to remind you of the first two laws of demoing. First law of demos is that demos will always crash.&lt;/p&gt;
    &lt;p&gt;And the second law of demos is that the probability of crashing goes up with the number of people watching." The audience laughed. "So if something goes wrong today, have some compassion for the demo-er." (p.115)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Make Unix taste sweet:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;NeXTSTEP was Steve's attempt to make Unix taste sweet. (p.141)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The World Wide Web on a NeXT Cube:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;By December, info.cern.ch went online. The World Wide Web now existed, running on a single black magnesium cube in CERN's Building 31. &lt;a href="/weblog/2023/08/06/quotes-from-weaving-the-web"&gt;[Tim Berners-Lee]&lt;/a&gt; scrawled a warning on the Cube in red ink: "This machine is a server. DO NOT POWER IT DOWN!!" If anyone disobeyed, then the entirety of the World Wide Web would disappear from the internet until Tim restarted the machine. (p.173)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What the FBI learned about Steve Jobs:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;The same year, President George H. W. Bush nominated Steve for a seat on the President's Export Council. The position required an FBI background check, which involved hundreds of hours of interviews with Steve's coworkers.&lt;/p&gt;
    &lt;p&gt;In these conversations, coworkers struggled to reconcile the visionary with the jerk. "Several individuals questioned Mr. Jobs' honesty, stating that Mr. Jobs will twist the truth and distort reality in order to achieve his goals," an FBI agent wrote. One source told the FBI Steve was both "deceptive" and perfect for a "high level political position," since "honesty and integrity are not required qualities to assume such a position." Another managed to pack the entire Steve paradox into a single breath, describing his personal life as "lacking due to his narcissism and shallowness," then saying, "he has far-reaching vision and can vitalize plans and goals." (p.178)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When traders became software customers:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;As NeXT's leaders studied the problem, a consensus began to emerge: they should abandon high-cost hardware in favor of selling high-margin software.&lt;/p&gt;
    &lt;p&gt;[…]&lt;/p&gt;
    &lt;p&gt;"Phil," he said, "you have to help me make Steve understand the value of NeXT computers to us is not in the hardware, it's in the software."&lt;/p&gt;
    &lt;p&gt;O'Connor made money by analyzing data and making fast trades in the market. NeXTSTEP allowed the firm to build proprietary trading software quickly, giving it an advantage over competitors.&lt;/p&gt;
    &lt;p&gt;The firm had developed a mutually dependent relationship with NeXT. O'Connor needed NeXT to survive so it could keep making NeXTSTEP. Without it, O'Connor would lose its edge. (p.188)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;NeXT's dependence on Motorola:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;The [Motorola's 88110] chip NeXT had bet everything on? Dead before it could ship. Motorola would fulfill existing orders, then cut NeXT of altogether. Mike summed up Motorola's message: "Fuck you and this new chip." Motorola had supplied every processor NeXT had ever used starting with the Cube. Now NeXT was left with a computer it had spent months building on top of a chip that it couldn't use.&lt;/p&gt;
    &lt;p&gt;[…]&lt;/p&gt;
    &lt;p&gt;"This is the single most important thing that ever happened to NeXT," Mike said.&lt;/p&gt;
    &lt;p&gt;Steve promised that he would never again let himself be dependent on other companies' road maps. He kept his word, spending $278 million to buy chipmaker P.A. Semi in 2008 after he returned to lead Apple. The purchase would allow Apple to design its own processors for the iPhone and iPad—and eventually create Apple silicon chips to power Macs. The lesson of NeXT's dependence on Motorola and other suppliers had stuck. (p. 200)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Silicon Valley, deals come first:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;Six months later, Steve's anger had cooled. As Bud would put it, they were "back on reasonable terms." Bud was now at Sun, and Sun had something Steve needed: powerful workstations that could run NeXTSTEP.&lt;/p&gt;
    &lt;p&gt;In Silicon Valley, rifts have an expiration date when there are deals to be made. (p.215)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A company consumed by bureaucracy:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;Steve's verdict on John was less generous. "What can I say?" he told interviewer Robert X. Cringely. "I hired the wrong guy."&lt;/p&gt;
    &lt;p&gt;He had watched from exile as John turned Apple into everything he despised: a bloated bureaucracy that confused activity with achievement, releasing more than seventy products while neglecting to consider what would make any one of them truly great. (p. 228)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;OpenStep:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;The operating system wars were beginning […]&lt;/p&gt;
    &lt;p&gt;[…]&lt;/p&gt;
    &lt;p&gt;Sun couldn't be caught unarmed. It needed its own object weapons to stand a chance. To get them, it briefly considered acquiring NeXT.&lt;/p&gt;
    &lt;p&gt;But it ultimately chose the cheaper option: licensing NeXT's software.&lt;/p&gt;
    &lt;p&gt;In the $10 million deal that emerged, Sun would work together with NeXT to develop a new program called OpenStep. OpenStep would botle some of NeXT's magic, making NeXT's revolutionary development tools available to programmers working on different computers.&lt;/p&gt;
    &lt;p&gt;Now developers could write software once and have it run on Sun workstations, IBM machines, or NeXT hardware—instead of being confined to NeXT platforms only. (p.235)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Doom was built on NeXT:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;As 1994 went on, a violent first-person shooter game called Doom, released at the end of the previous year, exploded around the world.&lt;/p&gt;
    &lt;p&gt;[…]&lt;/p&gt;
    &lt;p&gt;The NeXT team watched in awe as the game conquered millions of computers and reshaped the industry. They learned that John Carmack, the game's twenty-three-year-old creator, had built the entire thing on NeXT machines.&lt;/p&gt;
    &lt;p&gt;"The first major personal purchase I made wasn't a car, but rather a NeXT computer," John later wrote in a Facebook post. (p.245)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When the CEO works for the team:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;The transtormation was real and lasting. "The people who were with Steve by the end of '95 pretty much stayed with him for the rest of his life," Ed observed. By November 1995, Steve had become the kind of leader people wanted to follow long-term.&lt;/p&gt;
    &lt;p&gt;And he was humbled. "If you don't treat [talented workers] right, they can go get another job in ten minutes, right?" he later recounted to an interviewer. "So a strange thing happens, which is the sort of the hierarchy of power inverts, and the CEO is actually at the bottom. So I sort of feel like I work for most of these people, because they're the ones that are doing all the brilliant work, you know."&lt;/p&gt;
    &lt;p&gt;[…]&lt;/p&gt;
    &lt;p&gt;The man who had emerged by November 1995 was different. He was still demanding, still visionary, but tempered by failure and success in equal measure. (p.268)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fear of founder's syndrome:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;An Apple alumnus who had known Steve since the early eighties put it more colorfully. He called Ellen directly and, according to Heilemann, said: "Ellen, Steve is going to fuck Gil so hard his eardrums will pop." Ellen herself told Gil that she suspected Steve would succumb to "founderitis" —the inability of company founders to work under anyone else's authority. (p.297)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The top line drives the bottom line:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;Steve spent the summer of 1997 on a listening tour, gathering thoughts from senior managers on what had gone wrong at Apple. All he heard were tales of doom and gloom. He wanted to flip the script to one of optimism. In his presentations, he made it clear Apple had talented people but the wrong strategy. "Somebody taught me a long time ago… if you do the right things on the top line, the bottom line will follow," he told CNBC. (p.322)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rhapsody:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;Apple announced a "dual OS strategy": The company would support two operating systems simultaneously. The existing Mac OS would continue for current users, while a new system called &lt;a href="/weblog/2026/04/12/notes-on-darwin-and-the-macos-architecture#macos_and_its_origins"&gt;Rhapsody&lt;/a&gt; would offer NeXT's advanced capabilities on Apple computers. Rhapsody was essentially NeXTSTEP being ported to run on Apple hardware. (p.324)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Apple–Microsoft collaboration:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;Finally, Microsoft would invest $150 million in Apple for nonvoting stock—the public vote of confidence that the company desperately needed.&lt;/p&gt;
    &lt;p&gt;The investment was shrewd self-interest. Microsoft faced mounting antitrust scrutiny from the Department of Justice, which was threatening to break up the company. Keeping Apple alive gave Microsoft a competitor to point to, arguing they were not a monopoly. For $150 million-pocket change for Microsoft—Bill Gates bought himself a defense exhibit. (p.326)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Wall Street Unix:&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;In the 1990s, using a personal computer was like driving a car that might randomly stall in trathe. The old Mac OS crashed so routinely that people saved their work every few minutes out of paranoia. NeXT machines, built on the same Unix bedrock that ran Wall Street's trading systems, didn't break as easily. (p.339)&lt;/p&gt;
&lt;/blockquote&gt;</description><pubDate>Mon, 01 Jun 2026 08:07:35 +0000</pubDate><guid>https://marcarea.com/weblog/2026/06/01/quotes-from-steve-jobs-in-exile</guid></item><item><title>Deux décennies de tweets</title><link>https://marcarea.com/weblog/2026/05/06/deux-decennies-de-tweets</link><description>&lt;p&gt;J'ai archivé mes tweets sur près de deux décennies : 18 ans et un peu plus de 11 mois au moment où j'écris ces lignes. Le premier remonte au 07/05/2007.&lt;/p&gt;
&lt;p&gt;Au total, j'en avais publié près de &lt;b&gt;14&amp;nbsp;371&lt;/b&gt; sur mon site. Clairement trop.&lt;/p&gt;
&lt;p&gt;Non seulement le volume est devenu difficile à exploiter, mais le temps a passé&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;une grande partie des liens est morte&lt;/li&gt;
    &lt;li&gt;mes idées ont évolué&lt;/li&gt;
    &lt;li&gt;mon ton a changé&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;J'ai donc entrepris un nettoyage en profondeur et j'ai &lt;em&gt;trié mes tweets&lt;/em&gt;&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;~77 % ont été mis hors ligne (cela va sans doute encore augmenter)&lt;/li&gt;
    &lt;li&gt;normalisation des hashtags&lt;/li&gt;
    &lt;li&gt;corrections éditoriales ponctuelles&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce travail a permis de mettre en place &lt;a href="/tweets/stats"&gt;une page de statistiques&lt;/a&gt; plus utile, ainsi que quelques îlots de fun&amp;nbsp;:)&lt;/p&gt;
&lt;p&gt;En revisitant l'ensemble, trois grandes phases de mon usage du microblogging se dessinent&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;2007-2011 : un journal intime spontané et informel, centré sur le quotidien, avec beaucoup d'interactions sociales, d'argot et d'humour&lt;/li&gt;
    &lt;li&gt;2012-2019 : une transition vers le partage de contenus commentés, moins personnel, plus tourné vers l'actualité et des sujets de fond&lt;/li&gt;
    &lt;li&gt;2020-2026 : un usage éditorial structuré, plus sobre, centré sur la veille et l'analyse&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Certaines constantes demeurent tout du long : le hip-hop, la musique, l'animation japonaise, le jeu vidéo et l'humour.&lt;/p&gt;
&lt;p&gt;Je me suis ensuite demandé comment conserver une trace synthétique des tweets mis hors ligne. C'est précisément un type de travail pour lequel les LLM excellent. J'ai donc utilisé Sonnet 4.6 pour produire une chronologie des événements marquants liés aux grands thèmes du corpus&amp;nbsp;: Python, JavaScript, CSS, Apple, l'intelligence artificielle, ainsi que l'actualité politique et internationale.&lt;/p&gt;
&lt;p&gt;Je consigne le résultat ci-dessous, &lt;em&gt;em&amp;nbsp;dashes&lt;/em&gt; inclus&amp;nbsp;!&lt;/p&gt;

&lt;h2 id="python"&gt;Python&lt;/h2&gt;
&lt;ul&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2008–2009&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Python déjà utilisé comme langage principal pour des projets web&lt;/li&gt;
            &lt;li&gt;Django propulse le site personnel marcarea.com dès 2008 : "marcarea.com is now Django powered"&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2010–2011&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Django solidement installé comme framework de référence pour le web Python&lt;/li&gt;
            &lt;li&gt;Underscore.js comparé à Python — signe que Python influence les standards d'autres langages&lt;/li&gt;
            &lt;li&gt;L'idée du "full JS côté serveur" est dans l'air&lt;/li&gt;
            &lt;li&gt;
                &lt;code&gt;import antigravity&lt;/code&gt; et la culture Python — humour et PEPs omniprésents&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2012&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Backbone.js + Django comme stack hybride explorée&lt;/li&gt;
            &lt;li&gt;Python 2 encore majoritaire mais Python 3 commence à pointer — début des discussions sur la cohabitation Python 2/3&lt;/li&gt;
            &lt;li&gt;ECMAScript Harmony comparé à Python pour ses générateurs — Python en référence conceptuelle&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2013&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;AngularJS + Django : stack populaire dans les offres freelance parisiennes&lt;/li&gt;
            &lt;li&gt;Article de fond : "Backbone.js with Django 1.5" — Django comme backend d'API REST&lt;/li&gt;
            &lt;li&gt;Premiers retours d'expérience sur Django REST Framework&lt;/li&gt;
            &lt;li&gt;"Python is the new Perl" évoqué ironiquement&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2014&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Django 1.7 sort avec les migrations intégrées — une révolution pour le framework&lt;/li&gt;
            &lt;li&gt;Passage de MySQL à PostgreSQL documenté — Django + Postgres devient la stack de référence&lt;/li&gt;
            &lt;li&gt;Migration vers Python 3.4 et Django 1.7 sur tous les projets personnels : "j'ai terminé le passage"&lt;/li&gt;
            &lt;li&gt;Django REST Framework 3.0 en Kickstarter — la communauté finance directement son outillage&lt;/li&gt;
            &lt;li&gt;
                &lt;code&gt;virtualenvwrapper&lt;/code&gt; pour gérer les environnements virtuels — pip + virtualenv comme workflow standard&lt;/li&gt;
            &lt;li&gt;"Two Scoops of Django 1.6" lu et recommandé comme best practices de référence&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2015&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Python 3.5 sort avec &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; (PEP 492) — l'asynchrone natif arrive enfin&lt;/li&gt;
            &lt;li&gt;PEP 483/484 : les type hints font leur apparition — début du typage graduel&lt;/li&gt;
            &lt;li&gt;Django Channels annoncé : "a Big Damn Deal, pulling Django forward in a huge way" — WebSockets dans Django&lt;/li&gt;
            &lt;li&gt;Django 1.8 sort avec plusieurs moteurs de templates — ouverture vers Jinja2&lt;/li&gt;
            &lt;li&gt;Django 1.9 sort en fin d'année&lt;/li&gt;
            &lt;li&gt;pip-tools recommandé pour la gestion déterministe des dépendances&lt;/li&gt;
            &lt;li&gt;Ubuntu et Debian planifient Python 3 par défaut pour leurs prochaines LTS&lt;/li&gt;
            &lt;li&gt;Python passe devant PHP dans l'index Tiobe — "Whoa, Python is ahead of PHP on the Tiobe index now?!"&lt;/li&gt;
            &lt;li&gt;AWS SDK boto3 disponible pour Python 3&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2016&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Python 3.6 sort — dictionnaires ordonnés par défaut, f-strings, variables dict compactes&lt;/li&gt;
            &lt;li&gt;f-strings et type hints célébrés comme outils quotidiens dans le code&lt;/li&gt;
            &lt;li&gt;asyncio omniprésent mais contre-intuitif : "I don't understand Python's Asyncio. Quite frankly: it's not intuitive"&lt;/li&gt;
            &lt;li&gt;uvloop : asyncio "on steroids" — performance réseau en Python proche de Node.js&lt;/li&gt;
            &lt;li&gt;Série d'articles personnels sur les générateurs, itérateurs, coroutines, asyncio&lt;/li&gt;
            &lt;li&gt;Django Channels (WebSockets temps réel) en production : "Real-Time Django Is Here"&lt;/li&gt;
            &lt;li&gt;Django 1.10 sort&lt;/li&gt;
            &lt;li&gt;Python 3.6 3.5-3.6x plus rapide que Python 2.7 sur certains benchmarks&lt;/li&gt;
            &lt;li&gt;xkcd 1987 : l'environnement Python est un chaos — toujours d'actualité&lt;/li&gt;
            &lt;li&gt;"Python Packaging Is Good Now" — après des années de galère, pip + wheel fonctionnent&lt;/li&gt;
            &lt;li&gt;Grumpy (Google) : transcompilateur Python → Go pour contourner le GIL, remarqué&lt;/li&gt;
            &lt;li&gt;CPython migre sur GitHub — fin de l'ère Mercurial/bugs.python.org&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2017&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Python 3.7 en route avec &lt;code&gt;breakpoint()&lt;/code&gt;, dataclasses, dict ordonnés garantis&lt;/li&gt;
            &lt;li&gt;Type annotations popularisées : PEP-483 et PEP-484, typage graduel&lt;/li&gt;
            &lt;li&gt;Unicode et Python : article de fond&lt;/li&gt;
            &lt;li&gt;Guido van Rossum annonce sa retraite comme BDFL — choc dans la communauté Python&lt;/li&gt;
            &lt;li&gt;Stack Python/Django déclarée gagnante : "À un moment j'en ai eu marre de Django mais je n'ai jamais retrouvé aussi complet ni en Python ni en JavaScript"&lt;/li&gt;
            &lt;li&gt;Django + GraphQL (graphene-django) exploré&lt;/li&gt;
            &lt;li&gt;"The Incredible Growth of Python" (Stack Overflow) — croissance documentée et impressionnante&lt;/li&gt;
            &lt;li&gt;Déploiement Django documenté : Docker + uWSGI + Postgres&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2018&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Python 3.7.0 sorti officiellement&lt;/li&gt;
            &lt;li&gt;Guido van Rossum quitte son rôle de BDFL — fin d'une époque&lt;/li&gt;
            &lt;li&gt;"Say Goodbye to Virtualenv" — pipenv comme tentative de simplification&lt;/li&gt;
            &lt;li&gt;pip 10 sort, abandonne Python 2.6&lt;/li&gt;
            &lt;li&gt;Python 3 maintenant recommandé comme langage d'introduction à l'université&lt;/li&gt;
            &lt;li&gt;EOL Python 2 confirmée au 1er janvier 2020 — le compte à rebours commence&lt;/li&gt;
            &lt;li&gt;Django utilisé sur des projets beta.gouv.fr — adoption institutionnelle publique&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2019–2020&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Python 2 EOL le 1er janvier 2020 — fin officielle&lt;/li&gt;
            &lt;li&gt;pip 21.0 abandonne définitivement Python 2&lt;/li&gt;
            &lt;li&gt;Django 3.0 sort avec support ASGI&lt;/li&gt;
            &lt;li&gt;Django 3.1 : premières vues asynchrones disponibles — "ASYNC VIEWS ARE COMING"&lt;/li&gt;
            &lt;li&gt;"Second-guessing the modern web" : retour au Django "old-fashioned" après des SPAs React/Vue/Next&lt;/li&gt;
            &lt;li&gt;"I've built SPAs on Next.js, React and Vue.js. But guess what? In most cases there is no benefit. I'm back on Django."&lt;/li&gt;
            &lt;li&gt;Guido van Rossum rejoint Microsoft après sa retraite — surprise générale&lt;/li&gt;
            &lt;li&gt;Python 3.9 : support natif de la base de données de fuseaux horaires IANA dans la stdlib&lt;/li&gt;
            &lt;li&gt;Dramatiq, Huey évoqués comme alternatives légères à Celery pour les tâches asynchrones&lt;/li&gt;
            &lt;li&gt;Debut de la mission itou (emplois-inclusion.beta.gouv.fr) — projet majeur en Django/Postgres&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2021&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Match/case (PEP 634) accepté — le pattern matching arrive en Python 3.10&lt;/li&gt;
            &lt;li&gt;Django 3.2 LTS sorti&lt;/li&gt;
            &lt;li&gt;Django 4.0 sorti en décembre&lt;/li&gt;
            &lt;li&gt;Metaclasses en Python et Django : article de fond&lt;/li&gt;
            &lt;li&gt;Falcon (ASGI/WSGI) évoqué comme alternative minimaliste à Django&lt;/li&gt;
            &lt;li&gt;"It's time to leave Flask!" — Flask comme écosystème fragile et mal maintenu&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2022&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Ruff découvert : linter Python écrit en Rust, "blazing fast"&lt;/li&gt;
            &lt;li&gt;Pydantic v1 adopté pour la validation de données à l'exécution&lt;/li&gt;
            &lt;li&gt;Django Ninja découvert — API REST avec type hints et performances&lt;/li&gt;
            &lt;li&gt;Black adopté officiellement dans le codebase Django lui-même&lt;/li&gt;
            &lt;li&gt;"Boring Python: dependency management" et "Boring Python: code quality" — philosophie du pragmatisme&lt;/li&gt;
            &lt;li&gt;"Gestion ennuyeuse des dépendances Python" — article personnel&lt;/li&gt;
            &lt;li&gt;
                &lt;code&gt;ExitStack&lt;/code&gt; en Python : deux articles partagés comme découverte tardive mais utile&lt;/li&gt;
            &lt;li&gt;
                &lt;code&gt;ctx&lt;/code&gt; package PyPI compromis par prise de contrôle de domaine — supply chain PyPI vulnérable&lt;/li&gt;
            &lt;li&gt;
                &lt;code&gt;time-machine&lt;/code&gt; pour les tests temporels, &lt;code&gt;backoff&lt;/code&gt; pour les retries — outillage de qualité partagé&lt;/li&gt;
            &lt;li&gt;Suppression du GIL Python évoquée : "Removing the Python GIL" — la discussion s'intensifie&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2023&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Ruff confirmé comme remplacement de black + flake8 + isort + pylint — migration effectuée sur marcarea.com&lt;/li&gt;
            &lt;li&gt;Python 3.12 sorti — TypedDict pour les &lt;code&gt;kwargs&lt;/code&gt;, améliorations de performances&lt;/li&gt;
            &lt;li&gt;Django 5.0 sorti en décembre avec &lt;code&gt;GeneratedField&lt;/code&gt; (colonnes générées en base)&lt;/li&gt;
            &lt;li&gt;Suppression du GIL officiellement discutée au niveau CPython — PEP 703&lt;/li&gt;
            &lt;li&gt;Parsing error CVE-2023-24329 dans Python — faille de sécurité dans urllib&lt;/li&gt;
            &lt;li&gt;HTMX + Django : article "Django things you want with HTMX" — la combinaison gagne en popularité&lt;/li&gt;
            &lt;li&gt;N+1 queries dans Django REST Framework serializers : problème récurrent documenté&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2024&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Django 5.2 sort avec les composite primary keys — longtemps demandées&lt;/li&gt;
            &lt;li&gt;Python 3.13 en release candidate&lt;/li&gt;
            &lt;li&gt;
                &lt;code&gt;GeneratedField&lt;/code&gt; Django 5 célébrée comme killer feature&lt;/li&gt;
            &lt;li&gt;Implémentation complète de &lt;em&gt;Hypermedia Systems&lt;/em&gt; en Django (contact app, 12 chapitres) — projet open source&lt;/li&gt;
            &lt;li&gt;"Is async Django ready for prime time?" — la question reste ouverte&lt;/li&gt;
            &lt;li&gt;
                &lt;code&gt;django-filter&lt;/code&gt; CSVWidget découvert tardivement&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2025&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Django 20 ans : "Happy 20th Birthday Django" — juillet 2025&lt;/li&gt;
            &lt;li&gt;Django Tasks mergé dans Django core — tâches de fond natives sans Celery&lt;/li&gt;
            &lt;li&gt;Django 6.0 sorti en décembre&lt;/li&gt;
            &lt;li&gt;Python Documentary sortie remarquée&lt;/li&gt;
            &lt;li&gt;Litestar présenté comme le meilleur des nouveaux frameworks Python web&lt;/li&gt;
            &lt;li&gt;Django Debug Toolbar v6 sorti&lt;/li&gt;
            &lt;li&gt;Typing Python.org : la documentation des types Python reçoit son propre sous-domaine&lt;/li&gt;
            &lt;li&gt;Squashing des migrations Django : article de fond&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2026&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;marcarea.com mis à jour vers PostgreSQL 18.1, Python 3.14.2 et Django 6.0&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tendances de fond sur 17 ans :&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Django est le fil rouge absolument constant — jamais abandonné, régulièrement remis en question mais toujours revenu en grâce.
    &lt;/li&gt;
    &lt;li&gt;Le parcours suit le chemin Python 2 → Python 3 (laborieux, finalement abouti en 2020), puis la montée en puissance du typage graduel, de l'async natif, et de l'outillage Rust (Ruff, uv) qui redéfinit l'ergonomie du développement Python depuis 2022.
    &lt;/li&gt;
    &lt;li&gt;La philosophie reste constante : préférer le pragmatisme à la mode, et la stabilité d'un écosystème mature aux hypes successives.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="javascript"&gt;JavaScript&lt;/h2&gt;
&lt;ul&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2008–2009&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;jQuery omniprésent et incontournable pour tout ce qui touche au DOM&lt;/li&gt;
            &lt;li&gt;Node.js fait son apparition avec une hype "trop violente" — curiosité immédiate&lt;/li&gt;
            &lt;li&gt;Flash toujours présent, mais le sentiment que JS va le rendre inutile commence à émerger&lt;/li&gt;
            &lt;li&gt;JavaScript globalement considéré comme un langage "pourrave" mais indispensable&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2010–2011&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Node.js devient sérieux, "Bad Ass Rock Star Tech"&lt;/li&gt;
            &lt;li&gt;jQuery toujours roi : jQuery Deconstructed, event delegation, plugins en tous genres&lt;/li&gt;
            &lt;li&gt;d3.js émerge pour la visualisation de données&lt;/li&gt;
            &lt;li&gt;CoffeeScript remarqué pour son côté "pythonique"&lt;/li&gt;
            &lt;li&gt;L'idée du "full JS côté serveur" est dans l'air mais encore lointaine&lt;/li&gt;
            &lt;li&gt;Backbone.js commence à circuler comme premier framework MVC JS côté client&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2012&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Backbone.js en production, mais avec des doutes : "Don't use Backbone.js yet"&lt;/li&gt;
            &lt;li&gt;Underscore.js adopté comme utilitaire courant&lt;/li&gt;
            &lt;li&gt;L'ère des "MVC JavaScript frameworks" et de TodoMVC pour les comparer&lt;/li&gt;
            &lt;li&gt;ECMAScript Harmony en cours d'élaboration — le futur ES6 se dessine&lt;/li&gt;
            &lt;li&gt;Node.js discuté comme backend sérieux, même si encore perçu comme overkill&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2013&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;AngularJS domine les offres d'emploi et les meetups parisiens&lt;/li&gt;
            &lt;li&gt;React apparaît discrètement en fin d'année&lt;/li&gt;
            &lt;li&gt;Backbone.js vs Angular : le débat des MVC frameworks bat son plein&lt;/li&gt;
            &lt;li&gt;Première mission AngularJS transformée en mission Backbone : frustration réelle&lt;/li&gt;
            &lt;li&gt;SEO et AngularJS : problème identifié avec phantom.js comme rustine&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2014&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;React commence à convaincre : "taking best of Angular and doing it better"&lt;/li&gt;
            &lt;li&gt;Backbone et Angular toujours très demandés en freelance&lt;/li&gt;
            &lt;li&gt;Le paradigme "SPA par défaut" s'installe dans les esprits&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2015&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;ES6/ES2015 officiellement là&lt;/li&gt;
            &lt;li&gt;React monte rapidement dans les offres d'emploi&lt;/li&gt;
            &lt;li&gt;jQuery fête ses 10 ans — hommage nostalgique ("They don't remember how dark it was before your light")&lt;/li&gt;
            &lt;li&gt;Babel généralisé pour transpiler ES6 vers ES5&lt;/li&gt;
            &lt;li&gt;npm left-pad incident : le fiasco qui révèle la fragilité de l'écosystème npm&lt;/li&gt;
            &lt;li&gt;Webpack commence à faire son apparition (et déjà à faire souffrir)&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2016&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;"How it feels to learn JavaScript in 2016" — satire virale de la fatigue des frameworks JS&lt;/li&gt;
            &lt;li&gt;Vue.js 2.0 sort et crée un enthousiasme sincère : "the first thing in a long time that made me enjoy frontend dev again"&lt;/li&gt;
            &lt;li&gt;yarn lancé par Facebook comme alternative à npm&lt;/li&gt;
            &lt;li&gt;Svelte remarqué pour la première fois : "Vanilla JavaScript FTW", approche "frameworks without the framework"&lt;/li&gt;
            &lt;li&gt;Webpack : "Decided I was gonna learn webpack. 20 minutes later I was googling visa requirements to Hawaii"&lt;/li&gt;
            &lt;li&gt;JSX activement détesté : "tellement dégueulasse, on dirait des vieux PHP echo()"&lt;/li&gt;
            &lt;li&gt;React critiqué pour sa complexité, sa licence propriétaire et sa dépendance à Facebook&lt;/li&gt;
            &lt;li&gt;Async/await en JavaScript : encore peu compris, trouvé contre-intuitif&lt;/li&gt;
            &lt;li&gt;"How it feels to learn JS in 2016" — la JS fatigue est nommée et documentée&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2017&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Vue.js préféré face à React et Angular pour de nombreux projets&lt;/li&gt;
            &lt;li&gt;"React is the new Dojo" — analogie avec les frameworks oubliés du passé&lt;/li&gt;
            &lt;li&gt;Webpack toujours douloureux à configurer&lt;/li&gt;
            &lt;li&gt;Svelte creuse son sillon tranquillement&lt;/li&gt;
            &lt;li&gt;npm : premier malware (&lt;code&gt;crossenv&lt;/code&gt;) dans le registre public — la supply chain est fragile&lt;/li&gt;
            &lt;li&gt;Ryan Dahl liste ses regrets sur Node.js&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2018&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;"The Cost of JavaScript in 2018" — la surcharge de JS dans les pages web dénoncée&lt;/li&gt;
            &lt;li&gt;"I'm sick of all this bloated JavaScript all over the web. This is madness."&lt;/li&gt;
            &lt;li&gt;Web Components évoqués comme potentielle sortie de crise, mais avec scepticisme sur l'implémentation cross-browser&lt;/li&gt;
            &lt;li&gt;Webpack toujours haï : "Dear Webpack 4.0, Fuck You."&lt;/li&gt;
            &lt;li&gt;Cypress découvert comme framework de tests E2E, très apprécié&lt;/li&gt;
            &lt;li&gt;Héritage et classes en JavaScript : article de fond pour démystifier le prototype&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2019–2020&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;"Second-guessing the modern web" : remise en question profonde des SPAs React/Vue/Next.js&lt;/li&gt;
            &lt;li&gt;"I've built SPAs on Next.js, React and Vue.js. But guess what? In most cases there is no benefit. I'm back on old-fashioned Django."&lt;/li&gt;
            &lt;li&gt;SPA fatigue officiellement diagnostiquée dans la communauté&lt;/li&gt;
            &lt;li&gt;"The level of abstraction that React works on… is too high"&lt;/li&gt;
            &lt;li&gt;jQuery toujours aimé : "J'aime toujours jQuery !"&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2021&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Vite émerge comme nouveau bundler : "THIS IS SPARTA !" — enthousiasme immédiat face à la lenteur de Webpack&lt;/li&gt;
            &lt;li&gt;Deno gagne en visibilité&lt;/li&gt;
            &lt;li&gt;Programmation asynchrone JS : article de fond sur les Promises&lt;/li&gt;
            &lt;li&gt;IIFE ("immediately invoked functions") qualifiées d'"ancient times" — le JS moderne a bien changé&lt;/li&gt;
            &lt;li&gt;"js;dr = JavaScript required; Didn't Read" — le culte du JS-minimal refait surface&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2022&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;HTMX découvert et partagé massivement : "a bold prediction but it's true that htmx shouldn't exist: it's how normal HTML should work"&lt;/li&gt;
            &lt;li&gt;"Moving From React to htmx" — premier retour d'expérience concret d'une migration&lt;/li&gt;
            &lt;li&gt;"The frontend community wants to wash its hands of the decade we lost without naming the causes" — mea culpa collectif sur les SPAs&lt;/li&gt;
            &lt;li&gt;"SPA by default" dénoncé comme dogme&lt;/li&gt;
            &lt;li&gt;"React isn't great at anything except being popular"&lt;/li&gt;
            &lt;li&gt;Nouvelle vague de frameworks JS analysée : "we should return to more simplicity"&lt;/li&gt;
            &lt;li&gt;Proposal Temporal (nouvelle API Date/Time) pour ECMAScript partagé&lt;/li&gt;
            &lt;li&gt;"Les cinq règles d'or du JavaScript moderne" — article personnel&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2023&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;HTMX devient un vrai mouvement : "HTMX is the Future", "SPAs are simply too costly and time-consuming"&lt;/li&gt;
            &lt;li&gt;"The Great Gaslighting of the JavaScript Era" — révisionnisme historique sur la décennie des SPAs&lt;/li&gt;
            &lt;li&gt;"The Market for Lemons" — "modern web development is an expensive failure"&lt;/li&gt;
            &lt;li&gt;"A Historical Reference of React Criticism" — compilation des critiques accumulées&lt;/li&gt;
            &lt;li&gt;"I hate React.js with a fucking passion" partagé et validé&lt;/li&gt;
            &lt;li&gt;React memo et performances React : article de fond pour nuancer les critiques&lt;/li&gt;
            &lt;li&gt;Deno by Example publié&lt;/li&gt;
            &lt;li&gt;Cypress confirmé comme outil de référence pour les tests E2E&lt;/li&gt;
            &lt;li&gt;jQuery 4 beta sort — "jQuery changed my life !"&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2024&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;HTMX en plein essor : livre &lt;em&gt;Hypermedia Systems&lt;/em&gt; lu et implémenté sur un projet Django&lt;/li&gt;
            &lt;li&gt;"JavaScript Bloat in 2024" — le problème empire toujours&lt;/li&gt;
            &lt;li&gt;Alpine.js mentionné comme "modern jQuery replacement"&lt;/li&gt;
            &lt;li&gt;"I can't stand React SPAs anymore. They make me hate my job."&lt;/li&gt;
            &lt;li&gt;HTML, CSS et JS décrits comme "tightly coupled most of the time" — plaidoyer pour les approcher ensemble&lt;/li&gt;
            &lt;li&gt;"Most web developers assume this level of interactivity requires a SPA JS framework. It doesn't." — HTMX démontre son échelle&lt;/li&gt;
            &lt;li&gt;jQuery 4 beta : "jQuery is awesome !" — toujours de l'amour&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2025&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;"The future of htmx" — essai officiel sur la direction du projet&lt;/li&gt;
            &lt;li&gt;"You don't need JS" — article partagé, l'hypermedia as the engine of application state comme philosophie&lt;/li&gt;
            &lt;li&gt;"If not React, then what?" — Infrequently.org pousse la réflexion post-React&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2026&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Axios compromis sur npm — la supply chain npm reste un vecteur d'attaque majeur&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tendances de fond sur 17 ans :&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;jQuery → Backbone → AngularJS → React/Vue → fatigue des SPAs → retour au serveur avec HTMX
    &lt;/li&gt;
    &lt;li&gt;Le cycle complet d'une mode à sa contestation, avec en fil rouge une méfiance croissante envers la complexité accumulée par l'écosystème JavaScript, et un retour progressif aux fondamentaux du web (HTML, hypermedia, moins de JavaScript côté client).
    &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="css"&gt;CSS&lt;/h2&gt;
&lt;ul&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2009&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;
                &lt;code&gt;display: inline-block&lt;/code&gt; enfin utilisable en cross-browser&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2015&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Publication de CSS Cascade 4 (Last Call for Comments)&lt;/li&gt;
            &lt;li&gt;Flexbox passe en Candidate Recommendation (W3C)&lt;/li&gt;
            &lt;li&gt;Nouveau parseur CSS de Blink (+20% de performance)&lt;/li&gt;
            &lt;li&gt;CSS Transforms sans préfixe &lt;code&gt;-webkit-&lt;/code&gt; dans WebKit&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2016&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;CSS Variables débarquent dans WebKit&lt;/li&gt;
            &lt;li&gt;CSS Grid Layout commence à faire parler de lui&lt;/li&gt;
            &lt;li&gt;
                &lt;code&gt;@supports&lt;/code&gt; change la donne pour la détection de fonctionnalités&lt;/li&gt;
            &lt;li&gt;Débat CSS-in-JS vs CSS natif qui s'emballe (React, Vue, JSX)&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2017&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;CSS Grid officiellement disponible — "un truc de fou, on aurait dû avoir ça bien avant"&lt;/li&gt;
            &lt;li&gt;Fin du clearfix avec &lt;code&gt;flow-root&lt;/code&gt; — 20 ans après…&lt;/li&gt;
            &lt;li&gt;Flexbox : frustration sur le nommage (&lt;code&gt;flex-direction&lt;/code&gt; jugé contre-intuitif)&lt;/li&gt;
            &lt;li&gt;Firefox lance Quantum CSS (Stylo), moteur ultra-rapide&lt;/li&gt;
            &lt;li&gt;Débat autour du reset CSS (faut-il encore en utiliser un ?)&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2018&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;CSS Grid + CSS Multi-Columns combinés&lt;/li&gt;
            &lt;li&gt;CSS-in-JS à son pic de popularité — et de la résistance&lt;/li&gt;
            &lt;li&gt;"Braces to Pixels" — article sur le fonctionnement interne du moteur CSS&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2021&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Sortie de Namida, un CSS "classless" personnel pour styler l'HTML sémantique brut&lt;/li&gt;
            &lt;li&gt;
                &lt;code&gt;:focus-visible&lt;/code&gt; disponible dans WebKit&lt;/li&gt;
            &lt;li&gt;CUBE CSS découvert via le W3C design system&lt;/li&gt;
            &lt;li&gt;
                &lt;code&gt;pointer-events: none&lt;/code&gt; comme technique de superposition&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2022&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;
                &lt;code&gt;:has()&lt;/code&gt; — "change le game" mais limité à Safari à ce moment-là&lt;/li&gt;
            &lt;li&gt;
                &lt;code&gt;@layer&lt;/code&gt; (Cascade Layers) + &lt;code&gt;:where()&lt;/code&gt; = contrôle total sur la spécificité&lt;/li&gt;
            &lt;li&gt;
                &lt;code&gt;clamp()&lt;/code&gt; imbriqué pour les layouts responsives sans media queries&lt;/li&gt;
            &lt;li&gt;
                &lt;code&gt;!important&lt;/code&gt; — rappel de son origine réelle par le co-designer de CSS&lt;/li&gt;
            &lt;li&gt;State of CSS 2022 — bilan de l'état du langage&lt;/li&gt;
            &lt;li&gt;
                &lt;code&gt;:empty&lt;/code&gt; avec espaces blancs, débat sur l'évolution du sélecteur&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2024&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Conversion au utility-first CSS après des années de résistance : "So much time wasted to find CSS class names for single-use abstractions… I think I'm ready for utility-first CSS now"&lt;/li&gt;
            &lt;li&gt;CSS Grid utilisé pour imprimer de la notation musicale&lt;/li&gt;
            &lt;li&gt;Time-based CSS animations&lt;/li&gt;
            &lt;li&gt;Sticky table header en CSS — toujours aussi pénible (&lt;code&gt;position: sticky&lt;/code&gt; + bugs)&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2025&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;
                &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; enfin personnalisable en CSS — une demande vieille de 20 ans&lt;/li&gt;
            &lt;li&gt;Rappel de ne pas charger CSS/JS depuis des CDNs externes (performance + privacy)&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2026&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;CSS Grid Lanes (masonry layout) introduits dans WebKit — l'implémentation tant attendue du layout masonry natif&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="apple"&gt;Apple&lt;/h2&gt;
&lt;ul&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2008–2009&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Safari utilisé quotidiennement comme navigateur de développement — "If you're serious about front-end web development, there's pretty much only one OS choice"&lt;/li&gt;
            &lt;li&gt;Suppression du FireWire sur les nouveaux MacBook : "C'te loose, j'en aurai acheté un direct sinon"&lt;/li&gt;
            &lt;li&gt;iPhone omniprésent dans les tweets — jeux, apps, anecdotes&lt;/li&gt;
            &lt;li&gt;WWDC 2009 suivi en direct : MacBook Pro 13", Safari 4, Snow Leopard, iPhone 3GS&lt;/li&gt;
            &lt;li&gt;Snow Leopard remarqué pour un détail technique : 1 GB redéfini à 1 000 000 000 octets — "historique !"&lt;/li&gt;
            &lt;li&gt;Safari 4 : "une tuerie, comme il kick le ass !"&lt;/li&gt;
            &lt;li&gt;App Store : premières critiques de la politique d'Apple sur les développeurs ("ongoing karma leak" selon Paul Graham)&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2010&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;iPad annoncé le 27 janvier 2010 : suivi en direct du keynote, mèmes immédiats (Hitler répond à l'iPad), scepticisme assumé&lt;/li&gt;
            &lt;li&gt;Steve Jobs publie "Thoughts on Flash" — approuvé sans réserve&lt;/li&gt;
            &lt;li&gt;Apple Pay et l'approche d'Apple sur les brevets logiciels critiquée : "Steve Jobs a tort"&lt;/li&gt;
            &lt;li&gt;iPhone 4 : files d'attente incomprises — "Faire la queue des heures pour dépenser plus de 500€, je comprendrai jamais"&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2011&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;MacBook Pro Sandy Bridge attendu avec impatience, CB prête à fumer&lt;/li&gt;
            &lt;li&gt;Steve Jobs meurt le 5 octobre 2011 : "#rip Steve Jobs" — tweet minimaliste, sobre&lt;/li&gt;
            &lt;li&gt;iOS : suivi du tracking de localisation, désactivation documentée&lt;/li&gt;
            &lt;li&gt;Lion (OS X 10.7) : review d'Ars Technica relayée — "Siracusa est dans les starting-blocks, c'est ouf"&lt;/li&gt;
            &lt;li&gt;Siri annoncé&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2012&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;MacBook Pro Retina : iPad Retina wallpapers, enthousiasme visuel&lt;/li&gt;
            &lt;li&gt;Prince of Persia original pour Apple II : code source libéré, partagé avec nostalgie&lt;/li&gt;
            &lt;li&gt;Premier botnet Mac (Flashback) documenté&lt;/li&gt;
            &lt;li&gt;Safari 6 perd le backspace comme bouton Arrière — déploré&lt;/li&gt;
            &lt;li&gt;Apple retire Java de tous les navigateurs OS X&lt;/li&gt;
            &lt;li&gt;"Apple, la tyrannie du cool" (Arte) partagé&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2013&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;iOS 7 : suivi de la keynote, WWDC, nouvelles APIs Safari et HTML5&lt;/li&gt;
            &lt;li&gt;Jony Ive redesigns things (blog satirique) — humour affiché&lt;/li&gt;
            &lt;li&gt;OS X Mavericks&lt;/li&gt;
            &lt;li&gt;Editorial pour iPad : enthousiasme pour l'app (Python intégré)&lt;/li&gt;
            &lt;li&gt;"In 2007, Steve Jobs envisageait-il vraiment un iPhone WebApps only ?" — question historique partagée&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2014&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Trente ans du Macintosh : "Thirty Years of Mac" — commémoré&lt;/li&gt;
            &lt;li&gt;Shellshock corrigé par Apple&lt;/li&gt;
            &lt;li&gt;Mac mini : RAM soudée sur les nouveaux modèles — "#DealBreaker"&lt;/li&gt;
            &lt;li&gt;OS X Yosemite&lt;/li&gt;
            &lt;li&gt;Safari inclut DuckDuckGo — réaction positive&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2015&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;MacBook 2015 : "Repairability Score 1 out of 10" (iFixit) — partagé avec inquiétude&lt;/li&gt;
            &lt;li&gt;El Capitan (OS X 10.11) : System Integrity Protection analysée comme un grand changement architecturel&lt;/li&gt;
            &lt;li&gt;"Why Apple Defends Encryption" : Apple perçue comme rempart face à la surveillance&lt;/li&gt;
            &lt;li&gt;Safari 9&lt;/li&gt;
            &lt;li&gt;AirDrop pour iOS 7, "il était temps les mecs !"&lt;/li&gt;
            &lt;li&gt;Chargeur MacBook disséqué techniquement — fascination pour les internals Apple&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2016&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;MacBook Pro 2016 avec Touch Bar : réaction immédiate et franchement négative — "t'as un mac sans touche ESC t'entends, c'est le turfu !" (ironie)&lt;/li&gt;
            &lt;li&gt;"My 2011 MacBook Pro is immortal and it has an ethernet port FTW" — attachement durable à l'ancienne machine&lt;/li&gt;
            &lt;li&gt;Apple Pay France lancé : "J'ai fait mon 1er paiement Apple Pay, j'ai trop kiffé payer avec mon phone"&lt;/li&gt;
            &lt;li&gt;AirPods annoncés : "Can't wait to try them out !"&lt;/li&gt;
            &lt;li&gt;Litige fiscal irlandais : Tim Cook écrit une lettre à la communauté européenne — réfutée point par point par des experts fiscaux&lt;/li&gt;
            &lt;li&gt;iOS 10&lt;/li&gt;
            &lt;li&gt;macOS Sierra : comportement SSH/ssh-agent modifié, frustration des développeurs&lt;/li&gt;
            &lt;li&gt;WWDC 2016 : Swift Playgrounds, "the most approachable programming environment from Apple since HyperCard"&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2017&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;macOS High Sierra : faille root béante découverte — "I rarely describe any bug as inexcusable, but this is inexcusable" (Gruber) — deux failles majeures en peu de temps jugées inadmissibles pour une société "qui se targue tant de vouloir sécuriser les données"&lt;/li&gt;
            &lt;li&gt;iOS 11 : HEIF/HEVC, nouveaux formats image/vidéo analysés techniquement&lt;/li&gt;
            &lt;li&gt;Safari Technology Preview supporte &lt;code&gt;&amp;lt;img src=".mp4"&amp;gt;&lt;/code&gt; — trouvé "trop cool"&lt;/li&gt;
            &lt;li&gt;Steve Jobs : "All the work that I have done in my life will be obsolete by the time I'm 50" — citation partagée&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2018&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Spectre/Meltdown : article Apple sur les vulnérabilités des processeurs ARM/Intel partagé&lt;/li&gt;
            &lt;li&gt;Pas de MacBook sans TouchBar : déception&lt;/li&gt;
            &lt;li&gt;"Apple contributed to the Open Source community" — contribution macOS/Unix&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2019&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;1Password : longue fidélité à la version 7, meilleur gestionnaire de mots de passe&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2020&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Apple M1 annoncé : "How unified memory blows the SoCs off the M1 Macs" — enthousiasme technique immédiat&lt;/li&gt;
            &lt;li&gt;Touch Bar : "Fuck the Touch Bar", dernière chance avant le passage au M1&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2021&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;MacBook Pro M1 reçu : "Le M1 est arrivé. Il n'y a plus de Touch Bar. J'ai du mal à retenir mes larmes… de joie"&lt;/li&gt;
            &lt;li&gt;Passage au M1 célébré : remap des raccourcis clavier, gist publié sur GitHub&lt;/li&gt;
            &lt;li&gt;Safari 15 : "Ugliest tab design known to mankind" — réaction très vive, envisage de changer de navigateur&lt;/li&gt;
            &lt;li&gt;Apple TV + Magic Keyboard + OQEE : combo parfait&lt;/li&gt;
            &lt;li&gt;Death to the Touch Bar : fin officielle saluée ("Death to the fucking Touch Bar at last!")&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2022&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;macOS Ventura System Settings redesigné : "weird stuff found" — réaction mitigée&lt;/li&gt;
            &lt;li&gt;Safari : StopTheMadness pour bloquer les vidéos en autoplay vantée — "Killer feature"&lt;/li&gt;
            &lt;li&gt;Anne van Kesteren rejoint l'équipe WebKit chez Apple — réaction positive&lt;/li&gt;
            &lt;li&gt;macOS scan antimalware documenté (XProtect Remediator)&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2023&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;OCR intégré dans Preview macOS : "ça kick le ass, ça rox du ponaaay"&lt;/li&gt;
            &lt;li&gt;1Password 7.9.12 crash après mise à jour : bug traqué et documenté&lt;/li&gt;
            &lt;li&gt;Safari 17&lt;/li&gt;
            &lt;li&gt;QR Code Scanner intégré iOS : astuce partagée via Daring Fireball&lt;/li&gt;
            &lt;li&gt;iCloud Photos suppression : "tellement peu intuitif que ça oblige les gens à payer pour iCloud+"&lt;/li&gt;
            &lt;li&gt;Maccy + Quicksilver : fidélité au combo open source, résistance aux nouveaux launchers&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2024&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Apple Passwords app annoncée à la WWDC — "Passwords app for Mac, iPhone, iPad, Vision Pro and Windows"&lt;/li&gt;
            &lt;li&gt;macOS 15.2 casse la réplication SuperDuper : "A terrible, awful surprise"&lt;/li&gt;
            &lt;li&gt;Back to Space Black : tweet avec photo du nouveau MacBook&lt;/li&gt;
            &lt;li&gt;Apple Numbers exporte en CSV avec des points-virgules selon la locale — bug documenté avec résolution&lt;/li&gt;
            &lt;li&gt;iPhone transfer entre devices : "scared the hell out of me"&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2025&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Bootable macOS backups do not matter anymore : article personnel&lt;/li&gt;
            &lt;li&gt;macOS Tahoe : corner radii jugés trop petits — critique esthétique immédiate&lt;/li&gt;
            &lt;li&gt;WWDC 2025 : "Meet Liquid Glass" — nouvelle direction design partagée&lt;/li&gt;
            &lt;li&gt;macOS native window tiling découvert tardivement : "I didn't realize macOS had introduced this"&lt;/li&gt;
            &lt;li&gt;AirPods : traduction temps réel testée au Japon — "Can Apple's AirPod Translation Get You Through Tokyo?"&lt;/li&gt;
            &lt;li&gt;Saracroche sur l'App Store : 16 millions de numéros bloqués&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2026&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;50 ans d'Apple (1er avril 2026) : Tim Cook célèbre l'anniversaire, relayé&lt;/li&gt;
            &lt;li&gt;Switch de 1Password 7 vers Apple Passwords : "Surprisingly smooth so far"&lt;/li&gt;
            &lt;li&gt;Notes on Darwin and the macOS Architecture : article personnel&lt;/li&gt;
            &lt;li&gt;Anthropic's Mythos et Project Glasswing pour les appareils Apple : suivi des implications IA&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tendances de fond sur 17 ans :&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;La relation avec Apple est celle d'un utilisateur professionnel intensément attaché, jamais aveugle.
    &lt;/li&gt;
    &lt;li&gt;L'arc narratif va d'un enthousiasme total en 2009 ("Tha Apple Fanboy is in da place") à une affection adulte et exigeante en 2026.
    &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="intelligence_artificielle"&gt;Intelligence artificielle&lt;/h2&gt;
&lt;ul&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2018 : premiers signaux faibles&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Machine Learning Guides de Google partagé — curiosité professionnelle naissante&lt;/li&gt;
            &lt;li&gt;"It's time for schools to teach Python, not Java — it's the dominant language of AI and computer vision" — Python et IA comme couple naturel déjà reconnu&lt;/li&gt;
            &lt;li&gt;Prototype d'agent conversationnel avec Dialogflow réalisé "en quelques heures" — premier contact pratique avec les chatbots&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2020 : GPT-3, le premier frisson&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;GPT-3 analysé via "Webshit weekly" — "In love with the GPT-3 analysis" — le modèle d'OpenAI fait sensation sur Hacker News, lu avec délectation ironique&lt;/li&gt;
            &lt;li&gt;"Machine learning is just statistics!" suivi du gif d'une explosion nucléaire — le débat sémantique sur le ML partagé avec humour&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2022 : ChatGPT — le basculement&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Décembre 2022 : ChatGPT lancé, deux tweets en quelques jours&lt;/li&gt;
            &lt;li&gt;"ChatGPT écrit des textes bien meilleurs que ce que font beaucoup d'humains, y compris dans un environnement professionnel. Notamment, il ne fait aucune faute d'orthographe ou de grammaire." — observation factuelle et sans catastrophisme&lt;/li&gt;
            &lt;li&gt;"ChatGPT: The Future of AI Is Here"&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2023 : de la curiosité à l'étude sérieuse&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;"To replace programmers with AI, product managers need to accurately describe what they want. We're safe." — blague professionnelle qui cache une vraie réflexion&lt;/li&gt;
            &lt;li&gt;Adobe Photoshop Generative Fill : "Holly F Shit" — première réaction à l'IA générative d'image dans un outil mainstream&lt;/li&gt;
            &lt;li&gt;Jay-Z voice entièrement générée par IA : "🤯" — la voix clonable comme seuil franchi&lt;/li&gt;
            &lt;li&gt;Début de la lecture de "How AI Works: From Sorcery to Science" — l'approche devient celle d'un apprenant sérieux, pas d'un spectateur&lt;/li&gt;
            &lt;li&gt;"Simply put, researchers don't know why large language models like GPT-4 do what they do" — l'opacité fondamentale des LLMs notée et inquiétante&lt;/li&gt;
            &lt;li&gt;Prompt engineer : "ça a déjà créé un nouveau type d'emploi" — ton légèrement ironique mais prise de conscience réelle&lt;/li&gt;
            &lt;li&gt;Diffusion models et mains mutilées : "I promise I didn't ask for a picture of a woman like the Mona Lisa with horribly mutated hands" — le principal défaut des générateurs d'images documenté par l'expérience&lt;/li&gt;
            &lt;li&gt;Putin parle anglais couramment via IA : traduction de l'audio à la volée&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2024 : immersion dans les fondamentaux&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Janvier 2024 : lecture approfondie de "How AI Works" — neural networks, initialisation aléatoire, GPT-3 à 175 milliards de poids, GPT-4 à 1,7 trillion — les chiffres donnent le vertige&lt;/li&gt;
            &lt;li&gt;"730 pages ! Le deep learning c'est vraiment deep, ils ont pas menti." — "Deep Learning: A Visual Approach" attaqué avec sérieux&lt;/li&gt;
            &lt;li&gt;"Rage Against the Machine Learning" — le mème signé RATM, relayé avec humour&lt;/li&gt;
            &lt;li&gt;"Finished the first reading of How AI Works" — premier survol complet, une deuxième lecture prévue&lt;/li&gt;
            &lt;li&gt;"AI/machine learning at its finest" — à propos de Live Text d'Apple : la meilleure IA est celle qu'on ne voit pas&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2025 : basculement vers l'usage actif&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;"I'm about halfway through this book and I'm a bit stunned by the amount of trial and error in machine learning" — l'empirisme du domaine toujours aussi surprenant&lt;/li&gt;
            &lt;li&gt;"#AI is impressive because we've failed at semantic web and personal computing" — grille de lecture originale et perspicace : l'IA comme réponse aux promesses non tenues du web&lt;/li&gt;
            &lt;li&gt;Juin 2025 : "All progress on LLMs could halt today, and LLMs would remain the 2nd most important thing to happen over the course of my career" — endorsement de "My AI Skeptic Friends Are All Nuts" (fly.io) — l'enthousiasme devient assumé&lt;/li&gt;
            &lt;li&gt;"#AI for coding is like using Photoshop to enhance your photos" — métaphore élégante : l'IA amplifie les compétences sans les remplacer&lt;/li&gt;
            &lt;li&gt;Gemini CLI : Simon Willison analyse le system prompt — partagé immédiatement&lt;/li&gt;
            &lt;li&gt;Google Veo 3 deepfake YouTube : "Already Deepfaking All of YouTube's Most Smooth-Brained Content" — ironie mordante sur l'usage réel&lt;/li&gt;
            &lt;li&gt;Gen AI soul version de 50 Cent : "🤯"&lt;/li&gt;
            &lt;li&gt;Août 2025 : "I've been pairing with Claude Code for a couple of weeks. This changes everything. You've got to try this before pooping on AI." — conversion personnelle publique et franche après des semaines d'usage&lt;/li&gt;
            &lt;li&gt;"The thinking words Claude Code uses are funny" — curiosité sur les mécanismes internes, source code analysé&lt;/li&gt;
            &lt;li&gt;"I didn't wait for AI to come across buggy or non-working code. Now, every new reasoning language model is better than the previous ones." — adoption définitive, suivi de la progression des modèles&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2025 : génération vidéo et nouvelles frontières&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Seedance 2.0 : "This sports footage was generated entirely by AI" — l'indiscernabilité visuelle atteinte&lt;/li&gt;
            &lt;li&gt;Vidu Q3 au Japon : "2026 is becoming 'the year an individual can own an anime studio'" — la démocratisation créative documentée&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2026 : normalisation et recul analytique&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;"Notes on Deep Learning" publiées sur marcarea.com — synthèse personnelle des apprentissages&lt;/li&gt;
            &lt;li&gt;"Let's talk about LLMs" (b-list.org) : "writing code was never the main bottleneck. Speeding it up doesn't eliminate the harder parts of development. Because of that, LLMs are useful tools, not transformative solutions" — nuance partagée et enrichie : "What's still missing from many analyses is the improvement in code quality, and I think it's real."&lt;/li&gt;
            &lt;li&gt;Anthropic's Mythos et Project Glasswing pour Apple Devices — les implications produit suivies&lt;/li&gt;
            &lt;li&gt;Claude Code source map leaké : "Claude Code leaked their source map, effectively giving you a look into the codebase" — curiosité technique immédiate&lt;/li&gt;
            &lt;li&gt;Apple ML-SHARP : modèle open source pour la 3D Gaussian Splatting partagé&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tendances de fond sur 8 ans :&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;L'arc IA est celui d'une conversion progressive et honnête, sans l'enthousiasme naïf du premier jour ni le scepticisme défensif du praticien menacé.
    &lt;/li&gt;
    &lt;li&gt;Ce qui distingue cette trajectoire est l'absence de positionnement tribal. Ni "l'IA va tout changer en 6 mois", ni "c'est du marketing" — mais une curiosité outillée, une disposition à changer d'avis sur base de l'expérience directe, et la constance de publier ses notes pour mettre les apprentissages au propre, comme pour n'importe quel autre domaine technique.
    &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="news"&gt;Actualité politique &amp; internationale&lt;/h2&gt;
&lt;ul&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2009&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Crise financière de 2008 encore dans toutes les têtes : bonus bancaires, subprimes, Goldman Sachs&lt;/li&gt;
            &lt;li&gt;Affaire Clearstream suivie de près&lt;/li&gt;
            &lt;li&gt;Barack Obama taxe les grandes banques&lt;/li&gt;
            &lt;li&gt;Tunisie, Algérie : premières secousses sociales du monde arabe&lt;/li&gt;
            &lt;li&gt;Grippe A : vaccins et gestion gouvernementale critiquée&lt;/li&gt;
            &lt;li&gt;Disney rachète Marvel pour 4 milliards — réaction distante mais intéressée&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2010&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Crise de la zone euro en continu : Grèce, Portugal, Irlande, Espagne, Italie dans la tourmente&lt;/li&gt;
            &lt;li&gt;Les agences de notation (Moody's, Fitch, S&amp;amp;P) omniprésentes et très critiquées&lt;/li&gt;
            &lt;li&gt;Occupy Wall Street commence à germer intellectuellement&lt;/li&gt;
            &lt;li&gt;Goldman Sachs reconnaît son rôle dans la crise&lt;/li&gt;
            &lt;li&gt;Tunisie : Ben Ali chassé, WikiLeaks révèle la corruption&lt;/li&gt;
            &lt;li&gt;Haïti, Pakistan (inondations), Chili (séisme) : catastrophes naturelles suivies&lt;/li&gt;
            &lt;li&gt;Évasion fiscale : premiers signaux forts sur les paradis fiscaux&lt;/li&gt;
            &lt;li&gt;DSK à l'IMF, Lagarde au gouvernement : politique économique française décryptée&lt;/li&gt;
            &lt;li&gt;LOPPSI 2, HADOPI : surveillance numérique et droits en ligne suivis&lt;/li&gt;
            &lt;li&gt;Mondial de foot 2010 : Anelka/Domenech, la France s'écrase&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2011&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Printemps arabe : Tunisie, Égypte, Libye, Syrie, Bahreïn, Maroc — couverture intensive&lt;/li&gt;
            &lt;li&gt;Séisme et tsunami au Japon, accident de Fukushima porté au niveau 7&lt;/li&gt;
            &lt;li&gt;Crise de la zone euro atteint son pic : Grèce en quasi-faillite, Berlusconi démis, Italie sous pression&lt;/li&gt;
            &lt;li&gt;Occupy Wall Street : couverture photo et analytique&lt;/li&gt;
            &lt;li&gt;DSK arrêté à New York — choc politique français&lt;/li&gt;
            &lt;li&gt;Attentat d'Oslo : Breivik frappe la Norvège&lt;/li&gt;
            &lt;li&gt;Steve Jobs meurt le 5 octobre 2011&lt;/li&gt;
            &lt;li&gt;Dennis Ritchie (créateur de C et Unix) décède la même semaine — hommage sobre&lt;/li&gt;
            &lt;li&gt;Attali, Stiglitz, Krugman, Soros cités régulièrement comme voix d'alerte&lt;/li&gt;
            &lt;li&gt;Guantanamo leaks, WikiLeaks : secrets d'État exposés&lt;/li&gt;
            &lt;li&gt;LuxLeaks : premières révélations sur l'évasion fiscale luxembourgeoise&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2012&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Crise grecque : austérité musclée, manifestations, capitales en flammes&lt;/li&gt;
            &lt;li&gt;Megaupload fermé par le FBI — guerre au piratage&lt;/li&gt;
            &lt;li&gt;Costa Concordia : naufrage spectaculaire en Italie&lt;/li&gt;
            &lt;li&gt;Euro 2012 suivi avec passion&lt;/li&gt;
            &lt;li&gt;Nicolas Sarkozy battu : fin du sarkozysme analysé&lt;/li&gt;
            &lt;li&gt;Crise bancaire : BNP, Dexia, Commerzbank dans le viseur&lt;/li&gt;
            &lt;li&gt;ACTA (accord anti-contrefaçon) : mobilisation des internautes européens&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2013&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Affaire Heaulme : procès du double meurtre de Montigny-lès-Metz suivi avec une attention particulière (lien messin)&lt;/li&gt;
            &lt;li&gt;LuxLeaks : jugement des lanceurs d'alerte Antoine Deltour et Raphaël Halet&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2014&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Affaire Heaulme : renvoi en cour d'assises acté&lt;/li&gt;
            &lt;li&gt;Ukraine : crise de Crimée, début du conflit russo-ukrainien&lt;/li&gt;
            &lt;li&gt;Amazon, Apple, Google et fiscalité en Europe — début d'un feuilleton long&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2015 — janvier : #CharlieHebdo&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;7 janvier 2015 : attentat contre la rédaction de Charlie Hebdo — suivi en temps réel dès les premières heures&lt;/li&gt;
            &lt;li&gt;Prise d'otages porte de Vincennes le 9 janvier, revendication d'AQPA&lt;/li&gt;
            &lt;li&gt;11 janvier : Marche républicaine — "2h pour faire la moitié du boulevard Magenta, trop de monde"&lt;/li&gt;
            &lt;li&gt;Réflexions de fond sur la laïcité, l'islam politique, la montée du djihadisme&lt;/li&gt;
            &lt;li&gt;"L'Arabie saoudite, un Daesh qui a réussi" partagé — critique des alliés occidentaux dans la région&lt;/li&gt;
            &lt;li&gt;Etat islamique décrypté en profondeur : origines, tactiques, propagande&lt;/li&gt;
            &lt;li&gt;Intox et rumeurs démenties immédiatement (carte d'identité retrouvée, "no-go zones" Fox News)&lt;/li&gt;
            &lt;li&gt;Débat sur la pax laïca et la classification des religions&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2015 — suite de l'année&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;LuxLeaks : procès et condamnation des lanceurs d'alerte&lt;/li&gt;
            &lt;li&gt;COP21 à Paris : records de chaleur, urgence climatique documentée&lt;/li&gt;
            &lt;li&gt;Affaire Heaulme : procès médiatique et récit des chroniqueurs judiciaires&lt;/li&gt;
            &lt;li&gt;Brexit en préparation : premiers débats&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2015 — novembre : #AttaqueDeParis&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;13 novembre 2015, 22h26 : premier tweet en direct — "39 morts et 60 blessés, le bilan augmente toutes les 5 minutes"&lt;/li&gt;
            &lt;li&gt;Suivi minute par minute : Bataclan, Stade de France, République, état d'urgence décrété par Hollande&lt;/li&gt;
            &lt;li&gt;Fermeture des frontières, stations de métro fermées — informations pratiques partagées&lt;/li&gt;
            &lt;li&gt;14 novembre : revendication officielle de l'État islamique, communiqué audio en français&lt;/li&gt;
            &lt;li&gt;18 novembre : assaut à Saint-Denis, neutralisation d'Abdelhamid Abaaoud&lt;/li&gt;
            &lt;li&gt;Débat intense sur l'état d'urgence, les libertés civiles, la loi antiterroriste&lt;/li&gt;
            &lt;li&gt;Bombardements français sur Raqqa dès le 15 novembre : réaction militaire immédiate&lt;/li&gt;
            &lt;li&gt;Analyses géopolitiques : rôle de la Russie, de l'Arabie saoudite, de la coalition&lt;/li&gt;
            &lt;li&gt;"Le tout-sécuritaire ne suffira pas" (Piketty) — voix dissonantes partagées sans tabou&lt;/li&gt;
            &lt;li&gt;Intox démenties : femme kamikaze inexistante, tweets Trump rejetés comme "pignoufs"&lt;/li&gt;
            &lt;li&gt;Film "Made in France" sur le djihadisme&lt;/li&gt;
            &lt;li&gt;Réflexions sur la stratégie de Daech : "la déstabilisation ne surviendra qu'en multipliant les attaques à intervalles réguliers"&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2016&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Brexit : vote du 23 juin, "UK votes to leave EU" — choc et analyses&lt;/li&gt;
            &lt;li&gt;Attentat de Nice (14 juillet) : 86 morts sur la Promenade des Anglais&lt;/li&gt;
            &lt;li&gt;Élection de Trump : "La victoire de Trump avait été prédite par une intelligence artificielle"&lt;/li&gt;
            &lt;li&gt;Marine Le Pen : assistants fictifs au Parlement européen révélés&lt;/li&gt;
            &lt;li&gt;Affaire Barroso chez Goldman Sachs : scandale des portes tournantes UE&lt;/li&gt;
            &lt;li&gt;Panama Papers : évasion fiscale à l'échelle mondiale&lt;/li&gt;
            &lt;li&gt;LuxLeaks : jugement en appel, condamnation des lanceurs d'alerte maintenue&lt;/li&gt;
            &lt;li&gt;CumEx Files : fraude fiscale de 55 milliards révélée&lt;/li&gt;
            &lt;li&gt;Revenu universel : Finlande, Suisse (77% de non au référendum)&lt;/li&gt;
            &lt;li&gt;Hausse de l'extrême droite en Europe : Autriche, Hongrie, France&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2017&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Procès Heaulme : verdict — condamné, Patrick Dils définitivement réhabilité&lt;/li&gt;
            &lt;li&gt;Élection de Macron : "Make Our Planet Great Again", espoirs et scepticisme&lt;/li&gt;
            &lt;li&gt;Législatives : LREM domine, gauche fragmentée&lt;/li&gt;
            &lt;li&gt;Attentat des Champs-Élysées (avril 2017) suivi en direct&lt;/li&gt;
            &lt;li&gt;Ouragan Harvey au Texas&lt;/li&gt;
            &lt;li&gt;Record de chaleur 2016 : "la planète entre en territoire inconnu"&lt;/li&gt;
            &lt;li&gt;Bitcoin en bulle : "Bitcoin Bubble Makes Dot-Com Look Rational"&lt;/li&gt;
            &lt;li&gt;Jérusalem reconnue capitale d'Israël par Trump&lt;/li&gt;
            &lt;li&gt;Nicolas Hulot entre au gouvernement avec espoir&lt;/li&gt;
            &lt;li&gt;Vaccins obligatoires pour les enfants en France&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2018&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Crise financière italienne et populisme de gouvernement (Lega, M5S) suivis&lt;/li&gt;
            &lt;li&gt;"Microsoft to acquire GitHub for $7.5 billion" — grande nouvelle tech&lt;/li&gt;
            &lt;li&gt;Stan Lee décède à 95 ans&lt;/li&gt;
            &lt;li&gt;Bolsonaro élu au Brésil — "le Brésil prend le virage de l'extrême droite"&lt;/li&gt;
            &lt;li&gt;Nicolas Hulot quitte le gouvernement en direct à la radio&lt;/li&gt;
            &lt;li&gt;Commission européenne propose la fin de l'heure d'hiver&lt;/li&gt;
            &lt;li&gt;Pollution atmosphérique à Paris : niveaux records&lt;/li&gt;
            &lt;li&gt;Mondial 2018 : France championne du monde&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2019&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Brexit : atermoiements interminables au Parlement britannique&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2020 — #Covid19&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Mars 2020 : basculement brutal dans la pandémie — données, graphiques, analyses partagés quotidiennement&lt;/li&gt;
            &lt;li&gt;"Avant le Covid-19, environ 1676 décès par jour en France" — mise en perspective des chiffres&lt;/li&gt;
            &lt;li&gt;"Almost 25 million jobs could be lost worldwide as a result of COVID-19" (ILO)&lt;/li&gt;
            &lt;li&gt;Attestations de déplacement : le code open source d'un particulier repris par le gouvernement&lt;/li&gt;
            &lt;li&gt;Eurogroupe divisé après 16h de téléconférence : "L'Europe ne sortira pas indemne de la crise du Covid-19"&lt;/li&gt;
            &lt;li&gt;Plan de relance de 500 milliards — "moment hamiltonien" de l'Europe&lt;/li&gt;
            &lt;li&gt;"Trump's Losing Blame Game" : décryptage de la politisation américaine de la pandémie&lt;/li&gt;
            &lt;li&gt;"Les hélicoptères arrivent" et "The ECB Must Finance COVID-19 Deficits" : débat monétaire intense&lt;/li&gt;
            &lt;li&gt;Décembre 2020 : début de la vaccination en Europe, tweet sobre&lt;/li&gt;
            &lt;li&gt;2021 : chiffres macro partagés, impact économique de la France, optimisme prudent&lt;/li&gt;
            &lt;li&gt;2021 : débat sur le pass sanitaire, tensions parlementaires&lt;/li&gt;
            &lt;li&gt;2021 : variants, efficacité vaccinale réduite en Afrique du Sud, appel à lever les brevets&lt;/li&gt;
            &lt;li&gt;2021 : AstraZeneca suspendu par précaution en France&lt;/li&gt;
            &lt;li&gt;2024 : la Cour de justice de l'UE condamne la Commission européenne pour opacité sur les contrats vaccins&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2021&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Fury at Gove : exports britanniques vers l'UE chutent de 68% après Brexit&lt;/li&gt;
            &lt;li&gt;Déchets nucléaires à Bure : polémique environnementale&lt;/li&gt;
            &lt;li&gt;Twitter : Jack Dorsey remplacé par Parag Agrawal&lt;/li&gt;
            &lt;li&gt;Zemmour émerge comme candidat d'extrême droite&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2022&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Invasion de l'Ukraine par la Russie (24 février 2022) — couverture massive pendant des semaines&lt;/li&gt;
            &lt;li&gt;Menaces nucléaires russes, mise en alerte des forces de dissuasion&lt;/li&gt;
            &lt;li&gt;Centrale de Zaporijia prise sous les tirs — "Nuclear Power Plants As Weapons"&lt;/li&gt;
            &lt;li&gt;Macron perd la majorité absolue aux législatives&lt;/li&gt;
            &lt;li&gt;Crise énergétique : BCE et fragmentation de la zone euro&lt;/li&gt;
            &lt;li&gt;Canicules et incendies en Europe : Espagne, France, Grèce, Portugal&lt;/li&gt;
            &lt;li&gt;Assassinat d'Abe Shinzo au Japon&lt;/li&gt;
            &lt;li&gt;Liz Truss démissionne après 45 jours&lt;/li&gt;
            &lt;li&gt;FTX : effondrement spectaculaire de la plateforme crypto — suivi très dense&lt;/li&gt;
            &lt;li&gt;Scandale du Parlement européen (Eva Kaili) : corruption et Qatar&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2023&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Ukraine : un an de guerre, bilans et photographies&lt;/li&gt;
            &lt;li&gt;Réforme des retraites en France : 49.3, manifestations, crise institutionnelle&lt;/li&gt;
            &lt;li&gt;Credit Suisse racheté en urgence par UBS&lt;/li&gt;
            &lt;li&gt;Émeutes en France après la mort de Nahel&lt;/li&gt;
            &lt;li&gt;Conflit Hamas-Israël (7 octobre 2023) : "le sang de Soukkott" — suivi très intense&lt;/li&gt;
            &lt;li&gt;Séisme au Maroc&lt;/li&gt;
            &lt;li&gt;Fukushima : rejet des eaux traitées dans l'océan, controverse internationale&lt;/li&gt;
            &lt;li&gt;Incendies au Canada (record historique) et en Grèce&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2024&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Guerre Russie-Ukraine : Storm Shadow britanniques tirés sur la Russie, missile Oreshnik&lt;/li&gt;
            &lt;li&gt;Séisme au Japon (1er janvier 2024)&lt;/li&gt;
            &lt;li&gt;Législatives en France : dissolution surprise de Macron, victoire relative du NFP, puis Barnier&lt;/li&gt;
            &lt;li&gt;Marine Le Pen condamnée à 5 ans d'inéligibilité&lt;/li&gt;
            &lt;li&gt;Barnier renversé par motion de censure&lt;/li&gt;
            &lt;li&gt;Moody's dégrade la note de la France&lt;/li&gt;
            &lt;li&gt;Procès du financement libyen (Sarkozy) : 7 ans requis&lt;/li&gt;
            &lt;li&gt;Google paie 20 milliards à Apple pour Safari — révélation du procès antitrust&lt;/li&gt;
            &lt;li&gt;Jeux Olympiques de Paris 2024 : Aya Nakamura et la Garde républicaine&lt;/li&gt;
            &lt;li&gt;Incendie de la librairie Hisler à Metz — tweet ému&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2025&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Guerre Iran : bombardements américains et israéliens sur les sites nucléaires iraniens (juin 2025)&lt;/li&gt;
            &lt;li&gt;Extension de l'arsenal nucléaire français annoncée par Macron&lt;/li&gt;
            &lt;li&gt;Procès libyen de Sarkozy : dénouement suivi&lt;/li&gt;
            &lt;li&gt;Europe "colonie numérique" : article sur la dépendance technologique&lt;/li&gt;
            &lt;li&gt;Confiance dans la démocratie : seulement 23% des Américains font confiance au gouvernement fédéral&lt;/li&gt;
            &lt;li&gt;Tarifs douaniers Trump contre l'Europe : tensions commerciales&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;&lt;b&gt;2026&lt;/b&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Plus de la moitié des Britanniques favorables au retour dans l'UE (10 ans après le Brexit)&lt;/li&gt;
            &lt;li&gt;Guerre Israël-Iran toujours en cours, Liban et Golfe entraînés&lt;/li&gt;
            &lt;li&gt;Deterrence nucléaire européenne : "Pour la première fois depuis la Guerre froide, l'Europe bâtit une stratégie nucléaire qui lui appartient en propre"&lt;/li&gt;
            &lt;li&gt;Le cessez-le-feu en Iran décrit comme une défaite stratégique des États-Unis et d'Israël&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tendances de fond sur 17 ans :&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Une posture d'observateur engagé mais non partisan, toujours à la recherche de la mise en perspective analytique plutôt que de la réaction émotionnelle, même dans les moments les plus intenses comme la nuit du 13 novembre.
    &lt;/li&gt;
    &lt;li&gt;La géographie affective est aussi remarquable : Paris et Metz reviennent comme deux ancres permanentes, avec une sensibilité particulière pour tout ce qui touche la Lorraine.
    &lt;/li&gt;
&lt;/ul&gt;</description><pubDate>Wed, 06 May 2026 07:14:00 +0000</pubDate><guid>https://marcarea.com/weblog/2026/05/06/deux-decennies-de-tweets</guid></item><item><title>English tenses</title><link>https://marcarea.com/weblog/2026/04/25/english-tenses</link><description>&lt;figure class="full-width-break-out-of-container"&gt;
    &lt;table&gt;
        &lt;thead&gt;
            &lt;tr&gt;
                &lt;th&gt;Tenses&lt;/th&gt;
                &lt;th&gt;Signal words&lt;/th&gt;
                &lt;th&gt;Affirmative&lt;/th&gt;
                &lt;th&gt;Negative&lt;/th&gt;
                &lt;th&gt;Interrogative&lt;/th&gt;
            &lt;/tr&gt;
        &lt;/thead&gt;
        &lt;tbody&gt;

            &lt;!-- Present Simple --&gt;
            &lt;tr&gt;
                &lt;th rowspan="2"&gt;Present Simple&lt;/th&gt;
                &lt;td rowspan="2"&gt;
                    &lt;ul&gt;
                        &lt;li&gt;every day&lt;/li&gt;
                        &lt;li&gt;sometimes&lt;/li&gt;
                        &lt;li&gt;always&lt;/li&gt;
                        &lt;li&gt;often&lt;/li&gt;
                        &lt;li&gt;usually&lt;/li&gt;
                        &lt;li&gt;seldom&lt;/li&gt;
                        &lt;li&gt;never&lt;/li&gt;
                        &lt;li&gt;first … then&lt;/li&gt;
                    &lt;/ul&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I read&lt;/b&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I do not read&lt;/b&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;Do I read?&lt;/b&gt;
                &lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;
                    He reads
                    &lt;br&gt;
                    &lt;small&gt;&lt;mark&gt;third person singular -s&lt;/mark&gt;&lt;/small&gt;
                &lt;/td&gt;
                &lt;td&gt;He does not read&lt;/td&gt;
                &lt;td&gt;Does he read?&lt;/td&gt;
            &lt;/tr&gt;

            &lt;!-- Present Continuous --&gt;
            &lt;tr&gt;
                &lt;th rowspan="2"&gt;Present Continuous&lt;/th&gt;
                &lt;td rowspan="2"&gt;
                    &lt;ul&gt;
                        &lt;li&gt;now&lt;/li&gt;
                        &lt;li&gt;at the moment&lt;/li&gt;
                        &lt;li&gt;Look!&lt;/li&gt;
                        &lt;li&gt;Listen!&lt;/li&gt;
                    &lt;/ul&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I am reading&lt;/b&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I am not reading&lt;/b&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;Am I reading&lt;/b&gt;
                &lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;He is reading&lt;/td&gt;
                &lt;td&gt;He is not reading&lt;/td&gt;
                &lt;td&gt;Is he reading?&lt;/td&gt;
            &lt;/tr&gt;

            &lt;tr&gt;
                &lt;td colspan="5" style="border-top: 5px solid var(--color-gray-medium); padding: 0;"&gt;&lt;/td&gt;
            &lt;/tr&gt;

            &lt;!-- Present Perfect Simple --&gt;
            &lt;tr&gt;
                &lt;th rowspan="2"&gt;
                    Present perfect simple
                    &lt;br&gt;
                    &lt;small&gt;&lt;mark&gt;have/has&lt;/mark&gt;&lt;/small&gt;
                &lt;/th&gt;
                &lt;td rowspan="2"&gt;
                    &lt;ul&gt;
                        &lt;li&gt;just&lt;/li&gt;
                        &lt;li&gt;yet&lt;/li&gt;
                        &lt;li&gt;never&lt;/li&gt;
                        &lt;li&gt;ever&lt;/li&gt;
                        &lt;li&gt;already&lt;/li&gt;
                        &lt;li&gt;so far&lt;/li&gt;
                        &lt;li&gt;up to now&lt;/li&gt;
                        &lt;li&gt;since&lt;/li&gt;
                        &lt;li&gt;for&lt;/li&gt;
                        &lt;li&gt;recently&lt;/li&gt;
                    &lt;/ul&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I have seen&lt;/b&gt;
                    &lt;br&gt;
                    &lt;small&gt;&lt;mark&gt;past participle&lt;/mark&gt;&lt;/small&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I have not seen&lt;/b&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;Have I seen?&lt;/b&gt;
                &lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;He has seen&lt;/td&gt;
                &lt;td&gt;He has not seen&lt;/td&gt;
                &lt;td&gt;Has he seen?&lt;/td&gt;
            &lt;/tr&gt;

            &lt;!-- Present Perfect Continuous --&gt;
            &lt;tr&gt;
                &lt;th rowspan="2"&gt;
                    Present Perfect Continuous
                    &lt;br&gt;
                    &lt;small&gt;&lt;mark&gt;have/has&lt;/mark&gt;&lt;/small&gt;
                &lt;/th&gt;
                &lt;td rowspan="2"&gt;
                    &lt;ul&gt;
                        &lt;li&gt;all day&lt;/li&gt;
                        &lt;li&gt;the whole day&lt;/li&gt;
                        &lt;li&gt;how long&lt;/li&gt;
                        &lt;li&gt;since&lt;/li&gt;
                        &lt;li&gt;for&lt;/li&gt;
                    &lt;/ul&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I have been walking&lt;/b&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I have not been walking&lt;/b&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;Have I been walking&lt;/b&gt;
                &lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;He has been walking&lt;/td&gt;
                &lt;td&gt;He has not been walking&lt;/td&gt;
                &lt;td&gt;Has he been walking?&lt;/td&gt;
            &lt;/tr&gt;

            &lt;tr&gt;
                &lt;td colspan="5" style="border-top: 10px solid var(--color-black); padding: 0;"&gt;&lt;/td&gt;
            &lt;/tr&gt;

            &lt;!-- Past Simple --&gt;
            &lt;tr&gt;
                &lt;th rowspan="2"&gt;Past Simple&lt;/th&gt;
                &lt;td rowspan="2"&gt;
                    &lt;ul&gt;
                        &lt;li&gt;last…&lt;/li&gt;
                        &lt;li&gt;…ago&lt;/li&gt;
                        &lt;li&gt;in 1990&lt;/li&gt;
                        &lt;li&gt;yesterday&lt;/li&gt;
                    &lt;/ul&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I walked&lt;/b&gt;
                    &lt;br&gt;
                    &lt;small&gt;&lt;mark&gt;preterit&lt;/mark&gt;&lt;/small&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I did not walk&lt;/b&gt;
                    &lt;br&gt;
                    &lt;small&gt;&lt;mark&gt;auxiliary in the Past Simple&lt;/mark&gt;&lt;/small&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;Did I walk&lt;/b&gt;
                    &lt;br&gt;
                    &lt;small&gt;&lt;mark&gt;auxiliary in the Past Simple&lt;/mark&gt;&lt;/small&gt;
                &lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;He walked&lt;/td&gt;
                &lt;td&gt;He did not walk&lt;/td&gt;
                &lt;td&gt;Did he walk?&lt;/td&gt;
            &lt;/tr&gt;

            &lt;!-- Past Continuous --&gt;
            &lt;tr&gt;
                &lt;th rowspan="2"&gt;
                    Past Continuous
                    &lt;br&gt;
                    &lt;small&gt;&lt;mark&gt;was/were&lt;/mark&gt;&lt;/small&gt;
                &lt;/th&gt;
                &lt;td rowspan="2"&gt;
                    &lt;ul&gt;
                        &lt;li&gt;while&lt;/li&gt;
                    &lt;/ul&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I was reading&lt;/b&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I was reading&lt;/b&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;Was I reading&lt;/b&gt;
                &lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;They were reading&lt;/td&gt;
                &lt;td&gt;They were not reading&lt;/td&gt;
                &lt;td&gt;Were they reading?&lt;/td&gt;
            &lt;/tr&gt;

            &lt;tr&gt;
                &lt;td colspan="5" style="border-top: 5px solid var(--color-gray-medium); padding: 0;"&gt;&lt;/td&gt;
            &lt;/tr&gt;

            &lt;!-- Past perfect / Pluperfect --&gt;
            &lt;tr&gt;
                &lt;th rowspan="2"&gt;
                    Past Perfect simple
                    &lt;br&gt;
                    &lt;small&gt;&lt;i&gt;Pluperfect&lt;/i&gt;&lt;/small&gt;
                &lt;/th&gt;
                &lt;td rowspan="2"&gt;
                    &lt;ul&gt;
                        &lt;li&gt;already&lt;/li&gt;
                        &lt;li&gt;just&lt;/li&gt;
                        &lt;li&gt;never&lt;/li&gt;
                    &lt;/ul&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I had walked&lt;/b&gt;
                    &lt;br&gt;
                    &lt;small&gt;&lt;mark&gt;past participle&lt;/mark&gt;&lt;/small&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I had not walked&lt;/b&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;Had I walked&lt;/b&gt;
                &lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;He had walked&lt;/td&gt;
                &lt;td&gt;He had not walked&lt;/td&gt;
                &lt;td&gt;Had he walked?&lt;/td&gt;
            &lt;/tr&gt;

            &lt;!-- Past Perfect Continuous --&gt;
            &lt;tr&gt;
                &lt;th rowspan="2"&gt;Past Perfect Continuous&lt;/th&gt;
                &lt;td rowspan="2"&gt;
                    &lt;ul&gt;
                        &lt;li&gt;how long&lt;/li&gt;
                        &lt;li&gt;since&lt;/li&gt;
                        &lt;li&gt;for&lt;/li&gt;
                    &lt;/ul&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I had been reading&lt;/b&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;I had not been reading&lt;/b&gt;
                &lt;/td&gt;
                &lt;td&gt;
                    &lt;b&gt;Had I been reading&lt;/b&gt;
                &lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;He had been reading&lt;/td&gt;
                &lt;td&gt;He had not been reading&lt;/td&gt;
                &lt;td&gt;Had he been reading?&lt;/td&gt;
            &lt;/tr&gt;

            &lt;tr&gt;
                &lt;td colspan="5" style="border-top: 10px solid var(--color-black); padding: 0;"&gt;&lt;/td&gt;
            &lt;/tr&gt;

            &lt;!-- Future Present Continuous --&gt;
            &lt;tr&gt;
                &lt;th rowspan="7"&gt;Future&lt;/th&gt;
                &lt;th&gt;Present Continuous&lt;/th&gt;
                &lt;td&gt;
                    &lt;b&gt;I'm leaving&lt;/b&gt; tomorrow
                &lt;/td&gt;
                &lt;td colspan="2"&gt;
                    &lt;em&gt;I have already decided and arranged to do it&lt;/em&gt;
                &lt;/td&gt;
            &lt;/tr&gt;

            &lt;!-- Future Present Continuous --&gt;
            &lt;tr&gt;
                &lt;th&gt;Present Simple&lt;/th&gt;
                &lt;td&gt;
                    My train &lt;b&gt;leaves&lt;/b&gt; at 9:30
                &lt;/td&gt;
                &lt;td colspan="2"&gt;
                    &lt;em&gt;When we talk about timetables and programmes&lt;/em&gt;
                &lt;/td&gt;
            &lt;/tr&gt;

            &lt;!-- Future Be going to --&gt;
            &lt;tr&gt;
                &lt;th&gt;Be going to&lt;/th&gt;
                &lt;td&gt;
                    &lt;b&gt;He is going&lt;/b&gt; to read
                &lt;/td&gt;
                &lt;td colspan="2"&gt;
                    &lt;em&gt;I have already decided to do it, I intend to do it&lt;/em&gt;
                &lt;/td&gt;
            &lt;/tr&gt;

            &lt;!-- Future Will --&gt;
            &lt;tr&gt;
                &lt;th&gt;Will&lt;/th&gt;
                &lt;td&gt;
                    &lt;b&gt;He will read&lt;/b&gt;
                &lt;/td&gt;
                &lt;td colspan="2"&gt;
                    &lt;em&gt;What we know or believe about the future&lt;/em&gt;
                    &lt;br&gt;
                    &lt;em&gt;We have just decided to do something&lt;/em&gt;
                &lt;/td&gt;
            &lt;/tr&gt;

            &lt;!-- Future Continuous --&gt;
            &lt;tr&gt;
                &lt;th&gt;Future Continuous&lt;/th&gt;
                &lt;td&gt;
                    &lt;b&gt;I'll be leaving&lt;/b&gt; tomorrow
                &lt;/td&gt;
                &lt;td colspan="2"&gt;
                    &lt;em&gt;I will be in the middle of doing it&lt;/em&gt;
                &lt;/td&gt;
            &lt;/tr&gt;

            &lt;!-- Future Perfect --&gt;
            &lt;tr&gt;
                &lt;th&gt;Future Perfect&lt;/th&gt;
                &lt;td&gt;
                    &lt;b&gt;I'll have left&lt;/b&gt; by this time tomorrow
                &lt;/td&gt;
                &lt;td colspan="2"&gt;
                    &lt;em&gt;It will be complete before a time in the future&lt;/em&gt;
                &lt;/td&gt;
            &lt;/tr&gt;

            &lt;!-- Future Perfect Continuous --&gt;
            &lt;tr&gt;
                &lt;th&gt;Future Perfect Continuous&lt;/th&gt;
                &lt;td&gt;
                    Next year, &lt;b&gt;I'll have been working&lt;/b&gt; in the company for 30 years
                &lt;/td&gt;
                &lt;td colspan="2"&gt;
                    &lt;em&gt;Emphasise the duration of an activity in progress at a particular point in the future&lt;/em&gt;
                &lt;/td&gt;
            &lt;/tr&gt;

        &lt;/tbody&gt;
    &lt;/table&gt;
&lt;/figure&gt;

&lt;p&gt;Sources:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;a href="https://www.englisch-hilfen.de/en/grammar/english_tenses.htm"&gt;English Tenses&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="https://www.studocu.com/fr/document/le-mans-universite/anglais/recap-temps-anglais-recap-temps/36825219"&gt;&lt;i&gt;Récapitulatif sur les temps de l'anglais (PDF)&lt;/i&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><pubDate>Sat, 25 Apr 2026 06:37:19 +0000</pubDate><guid>https://marcarea.com/weblog/2026/04/25/english-tenses</guid></item><item><title>Awk</title><link>https://marcarea.com/weblog/2026/04/19/awk</link><description>&lt;p&gt;Notes from &lt;a href="https://awk.dev"&gt;&lt;em&gt;The AWK Programming Language&lt;/em&gt;&lt;/a&gt; (2024) by Alfred V. Aho, Brian W. Kernighan, and Peter J. Weinberger.&lt;/p&gt;

&lt;p&gt;I don't use Awk often, so this is my way of holding onto the essentials for later.&lt;/p&gt;

&lt;h2 id="what_is_awk"&gt;What is Awk?&lt;/h2&gt;

&lt;p&gt;Awk is a scripting language designed for text processing.&lt;/p&gt;

&lt;p&gt;An Awk program:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;is a sequence of &lt;strong&gt;pattern-actions&lt;/strong&gt; statements
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;patterns&lt;/strong&gt; can involve numeric and strings comparisons&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;actions&lt;/strong&gt; can include computation and formatted printing&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;every input line is tested against the patterns in order&lt;/li&gt;
&lt;li&gt;when a pattern matches, it performs the corresponding action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Awk:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reads through the input files automatically&lt;/li&gt;
&lt;li&gt;splits each input line into fields&lt;/li&gt;
&lt;li&gt;provides a number of built-in variables and functions&lt;/li&gt;
&lt;li&gt;lets us define our own variables and functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this combination of features, many useful computations can be expressed by short programs because the details that would be needed in another language are handled implicitely in Awk.&lt;/p&gt;

&lt;h2 id="awk_tutorial"&gt;Awk tutorial&lt;/h2&gt;

&lt;p&gt;Suppose you have a file called &lt;code&gt;emp.data&lt;/code&gt; with three fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name&lt;/li&gt;
&lt;li&gt;pay rate in dollars (per hour)&lt;/li&gt;
&lt;li&gt;number of hours worked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fields are separated by spaces or tabs:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Beth    21      0
Dan     19      0
Kathy   15.50   10
Mark    25      20
Mary    22.50   22
Susie   17      18
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To print names of employees who did not work:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '$3 == 0 { print $1 }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The part inside the quotes is a complete Awk program.&lt;/p&gt;

&lt;h3 id="structure_of_an_awk_program"&gt;Structure of an Awk program&lt;/h3&gt;

&lt;p&gt;An Awk program is a sequence of one or more pattern-action statements:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;pattern1 { action1 }
pattern2 { action2 }
…
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Awk processes input like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reads input line by line&lt;/li&gt;
&lt;li&gt;from any number of files&lt;/li&gt;
&lt;li&gt;checks each line against all patterns&lt;/li&gt;
&lt;li&gt;when a pattern matches, it runs the corresponding action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Either the pattern or the action (but not both) may be omitted:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;pattern only (&lt;code&gt;$3 == 0&lt;/code&gt;): prints every matching line&lt;/li&gt;
&lt;li&gt;action only (&lt;code&gt;{ print $1 }&lt;/code&gt;): the action is performed for every line&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Blank lines are ignored.&lt;/p&gt;

&lt;h3 id="running_an_awk_program"&gt;Running an Awk program&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;on files:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '$3 == 0 { print $1 }' file1 file2
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;from terminal input (until an end-of-file signal - &lt;code&gt;ctrl-D&lt;/code&gt;):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '$3 == 0 { print $1 }'
foo foo 0
foo
bar bar 1
baz baz 0
baz
^D
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;By loading its program from a file:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk -f progfile optional list of input files
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id="errors"&gt;Errors&lt;/h3&gt;

&lt;p&gt;If there's a mistake in an Awk program:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Syntax error" → there's a grammatical mistake (shown between &lt;code&gt;&amp;gt;&amp;gt;&amp;gt; &amp;lt;&amp;lt;&amp;lt;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;"Bailing out" → Awk stopped, no recovery was attempted&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="simple_output"&gt;Simple output&lt;/h2&gt;

&lt;p&gt;Awk works with only two types of data:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;numbers&lt;/li&gt;
&lt;li&gt;strings of characters&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A file like &lt;code&gt;emp.data&lt;/code&gt; is typical: each line contains words and numbers separated by spaces or tabs.&lt;/p&gt;

&lt;h3 id="printing_every_line"&gt;Printing every line&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;print&lt;/code&gt; by itself prints the current input line:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '{ print }' emp.data
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;$0&lt;/code&gt; is the whole line, so this is the same thing:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '{ print $0 }' emp.data
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id="printing_specific_fields"&gt;Printing specific fields&lt;/h3&gt;

&lt;p&gt;Fields are referenced as &lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;$2&lt;/code&gt;, &lt;code&gt;$3&lt;/code&gt;, etc.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Print specific fields:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '{ print $1 $3 }' emp.data
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Awk does not print whitespaces values by default, use commas to add whitespace:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '{ print $1, $3 }' emp.data
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Default behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;expressions separated by a comma in a &lt;code&gt;print&lt;/code&gt; statement are separated by a space&lt;/li&gt;
&lt;li&gt;each line produced by &lt;code&gt;print&lt;/code&gt; ends with a newline character&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="nf_number_of_fields"&gt;&lt;code&gt;NF&lt;/code&gt; - Number of fields&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;awk '{ print NF, $1, NF }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="computing_and_printing"&gt;Computing and printing&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;awk '{ print $1, $2 * $3 }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="nr_line_numbers"&gt;&lt;code&gt;NR&lt;/code&gt; - Line numbers&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;awk '{ print NR, $0 }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="putting_text_in_the_output"&gt;Putting text in the output&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;awk '{ print "Total pay for", $1, "is", $2 * $3 }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="formatted_output_with_printf"&gt;Formatted output with &lt;code&gt;printf&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;printf&lt;/code&gt; statement lets you control exactly how output looks:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;printf(format, value_1, value_2, … , value_n)
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;format&lt;/code&gt;: a string containing specifications that defines how values are displayed&lt;/li&gt;
&lt;li&gt;a specification is a &lt;code&gt;%&lt;/code&gt; followed by a few characters that control the format of a &lt;em&gt;value&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;the 1st specification tells how &lt;code&gt;value_1&lt;/code&gt; is to be printed&lt;/li&gt;
&lt;li&gt;the 2nd specification tells how &lt;code&gt;value_2&lt;/code&gt; is to be printed&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;%s&lt;/code&gt; → string&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%.2f&lt;/code&gt; → number with 2 decimal places&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%-8s&lt;/code&gt; → left-align text in 8-character width&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%6.2f&lt;/code&gt; → number in 6-character width, 2 decimals&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="important_difference_from_print"&gt;Important difference from &lt;code&gt;print&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;printf&lt;/code&gt;, no spaces or newlines are produced automatically, so don't forget the &lt;code&gt;\n&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '{ printf("Total pay for %s is $%.2f\n", $1, $2 * $3) }' emp.data

awk '{ printf("%-8s $%6.2f\n", $1, $2 * $3) }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="sorting_the_output"&gt;Sorting the output&lt;/h3&gt;

&lt;p&gt;The easiest way to sort in order of increasing pay is to prefix the total pay and run the output through a sorting program:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '{ printf("%6.2f %s\n", $2 * $3, $0) }' emp.data | sort
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="selection"&gt;Selection&lt;/h2&gt;

&lt;p&gt;A pattern by itself automatically prints all matching lines.&lt;/p&gt;

&lt;p&gt;That's why many Awk programs are just a single pattern.&lt;/p&gt;

&lt;h3 id="selection_by_comparison"&gt;Selection by comparison&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;awk '$2 &amp;gt;= 20' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="selection_by_computation"&gt;Selection by computation&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;awk '$2 * $3 &amp;gt; 200 { printf("$%.2f for %s\n", $2 * $3, $1) }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="selection_by_text_content"&gt;Selection by text content&lt;/h3&gt;

&lt;p&gt;Exact match:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '$1 == "Susie"' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;All lines that contain &lt;code&gt;Susie&lt;/code&gt; anywhere (regular expression):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '/Susie/' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="combinations_of_patterns"&gt;Combinations of patterns&lt;/h3&gt;

&lt;p&gt;Patterns can be combined with parentheses and the logical operators &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, &lt;code&gt;||&lt;/code&gt;, and &lt;code&gt;!&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '$2 &amp;gt;= 20 || $3 &amp;gt;= 20' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="data_validation"&gt;Data validation&lt;/h3&gt;

&lt;p&gt;Real-world data often contains errors.&lt;/p&gt;

&lt;p&gt;Awk is useful for checking that data is in the right format.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk 'NF != 3 { print $0, "number of fields is not equal to 3" }' emp.data

awk '$2 &amp;lt; 15 { print $0, "rate is too low" }' emp.data

awk '$2 &amp;gt; 25 { print $0, "rate exceeds $25 per hour"}' emp.data

awk '$3 &amp;lt; 0 { print $0, "negative hours worked" }' emp.data

awk '$3 &amp;gt; 60 { print $0, "too many hours worked" }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="begin_and_end_special_patterns"&gt;&lt;code&gt;BEGIN&lt;/code&gt; and &lt;code&gt;END&lt;/code&gt; special patterns&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;BEGIN&lt;/code&gt; runs before any input is processed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;END&lt;/code&gt; runs after all input is processed.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk 'BEGIN { print "NAME    RATE    HOURS"; print "" } { print }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notice that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;print ""&lt;/code&gt; prints a blank line&lt;/li&gt;
&lt;li&gt;&lt;code&gt;print&lt;/code&gt; prints the current input line&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="computing_with_awk"&gt;Computing with Awk&lt;/h2&gt;

&lt;p&gt;An action can contain multiple statements.&lt;/p&gt;

&lt;p&gt;Statements can be separated by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;new lines&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$3 &amp;gt; 15 { emp = emp + 1 }
END { print emp, "employes worked more than 15 hours" }
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;or semicolons&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$3 &amp;gt; 15 { emp = emp + 1 } ; END { print emp, "employes worked more than 15 hours" }
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="counting"&gt;Counting&lt;/h3&gt;

&lt;p&gt;Counts employees who worked more than 15 hours using an &lt;code&gt;emp&lt;/code&gt; variable:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '$3 &amp;gt; 15 { emp = emp + 1 } ; END { print emp, "employes worked more than 15 hours" }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Shorter version with the increment operator &lt;code&gt;++&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '$3 &amp;gt; 15 { emp++ } ; END { print emp, "employes worked more than 15 hours" }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Awk variables for numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;begin with the value &lt;code&gt;0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;aren't explicitely initialized&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="computing_sums_and_averages"&gt;Computing sums and averages&lt;/h3&gt;

&lt;p&gt;Count total employees using the built-in &lt;code&gt;NR&lt;/code&gt; variable:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk 'END { print NR, "employees" }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To compute the average pay:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '{ pay = pay + $2 * $3 } ; END { print NR, "employees" ; print "total pay is", pay ; print "average pay is", pay / NR }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="handling_text"&gt;Handling text&lt;/h3&gt;

&lt;p&gt;Awk variables can hold strings of characters.&lt;/p&gt;

&lt;p&gt;Find the employee who is paid the most per hour:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '
    $2 &amp;gt; maxrate { maxrate = $2; maxemp = $1 }
    END { print "highest hourly rate:", maxrate, "for", maxemp }
' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="string_concatenation"&gt;String concatenation&lt;/h3&gt;

&lt;p&gt;Collect all the employee names into a string by appending each name and a space to the previous value in the variable &lt;code&gt;names&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '{ names = names $1 " " } ; END { print names }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For each line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;the first statement concatenates 3 strings:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the previous value of &lt;code&gt;names&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the first field&lt;/li&gt;
&lt;li&gt;a space&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and assigns the resulting string to &lt;code&gt;names&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The string concatenation operator:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;is represented by writing string values one after the other&lt;/li&gt;
&lt;li&gt;there is no explicit concatenation operator&lt;/li&gt;
&lt;li&gt;in hindsight, this design might not be ideal because it can lead to hard-to-spot errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Awk variables for strings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;begin life with the value &lt;code&gt;null string&lt;/code&gt; (a string containing no characters)&lt;/li&gt;
&lt;li&gt;aren't explicitely initialized&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="printing_the_last_input_line"&gt;Printing the last input line&lt;/h3&gt;

&lt;p&gt;Built-in variables like &lt;code&gt;NR&lt;/code&gt; and &lt;code&gt;$0&lt;/code&gt; retain their value in an &lt;code&gt;END&lt;/code&gt; action.&lt;/p&gt;

&lt;p&gt;So one way to print the last line is:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk 'END { print $0 }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="built_in_functions"&gt;Built-in functions&lt;/h3&gt;

&lt;p&gt;Awk has built-in functions for computing values.&lt;/p&gt;

&lt;p&gt;To compute the length of each person's name:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '{ print $1, length($1) }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="counting_lines_words_and_characters"&gt;Counting lines, words, and characters&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;length&lt;/code&gt;, &lt;code&gt;NF&lt;/code&gt;, and &lt;code&gt;NR&lt;/code&gt; to count the number of lines, words, and characters:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '{ nc += length($0) + 1 ; nw += NF } END { print NR, "lines,", nw, "words,", nc, "characters" }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We added &lt;code&gt;1&lt;/code&gt; to &lt;code&gt;nc&lt;/code&gt; to count the newline character at the end of each input line.&lt;/p&gt;

&lt;h2 id="control_flow_statements"&gt;Control-flow statements&lt;/h2&gt;

&lt;p&gt;Control-flow statements are used inside actions to control how your program runs.&lt;/p&gt;

&lt;h3 id="if_else_statement"&gt;If-else statement&lt;/h3&gt;

&lt;p&gt;Use an &lt;code&gt;if&lt;/code&gt; to defend against any potential division by zero in computing the average pay:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '$2 &amp;gt; 20 { n++; pay += $2 * $3 } END {
  if (n &amp;gt; 0) {
    print n, "high-pay employees Total pay is", pay, "Average pay is", pay/n
  } else {
    print "No employees are paid more than $30/hour"
  }
}' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="while_statement"&gt;While statement&lt;/h3&gt;

&lt;p&gt;We write an Awk program in a file named &lt;code&gt;interest1.awk&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it shows how the value of an amount of money invested at a particular interest rate grows over a number of years&lt;/li&gt;
&lt;li&gt;using the formula &lt;code&gt;value = amount(1 + rate)^years&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- BR --&gt;

&lt;pre&gt;&lt;code&gt;# interest1.awk
# Compute compound interest
#   input:  amount  rate  years
#   output: compounded value at the end of each year

{   i = 1
    while (i &amp;lt;= $3) {
        printf("\t%.2f\n", $1 * (1 + $2) ^ i)
        i++
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this program:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;\t&lt;/code&gt; in the &lt;code&gt;printf&lt;/code&gt; specification string stands for a tab character&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;^&lt;/code&gt; is the exponentiation operator&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To use it:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk -f programs/interest1.awk
1000 .05 5

1000 .15 10
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="for_statement"&gt;For statement&lt;/h3&gt;

&lt;p&gt;Here is the previous interest computation with a &lt;code&gt;for&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# interest2.awk
# Compute compound interest
#   input:  amount  rate  years
#   output: compounded value at the end of each year

{ for (i = 1; i &amp;lt;= $3; i++)
    printf("\t%.2f\n", $1 * (1 + $2) ^ i)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="fizzbuzz"&gt;FizzBuzz&lt;/h3&gt;

&lt;p&gt;Print the numbers from 1 to 100:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if the number is divisible by 3, print "fizz"&lt;/li&gt;
&lt;li&gt;if the number is divisible by 5, print "buzz"&lt;/li&gt;
&lt;li&gt;if the number is divisible by both, print "fizzbuzz"&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- BR --&gt;

&lt;pre&gt;&lt;code&gt;awk 'BEGIN {
  for (i = 1; i &amp;lt;= 100; i++) {
    if (i%15 == 0)  # divisible by both 3 and 5
      print i, "fizzbuzz" 
    else if (i%5 == 0)
      print i, "buzz"
    else if (i%3 == 0)
      print i, "fizz"
    else
      print i
  }
}'
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="arrays"&gt;Arrays&lt;/h2&gt;

&lt;p&gt;Print the input in reverse order:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '
    { line[NR] = $0 }  # Remember each input line.
    END {
        for (i = NR; i &amp;gt; 0; i--) {
            print line[i]
        }
    }' emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The first action stores input lines in successive elements of the array &lt;code&gt;line&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the first line goes into &lt;code&gt;line[1]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the second line goes into &lt;code&gt;line[2]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;and so on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The expression inside &lt;code&gt;[]&lt;/code&gt; is called the &lt;em&gt;subscript&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The subscripts in this example are numeric but they can be arbitrary strings of characters.&lt;/p&gt;

&lt;h2 id="useful_one_liners"&gt;Useful one-liners&lt;/h2&gt;

&lt;figure class="full-width-break-out-of-container"&gt;
    &lt;table class="zebra"&gt;
        &lt;thead&gt;
            &lt;tr&gt;
                &lt;th&gt;Description&lt;/th&gt;
                &lt;th&gt;Command&lt;/th&gt;
            &lt;/tr&gt;
        &lt;/thead&gt;
        &lt;tbody&gt;
            &lt;tr&gt;
                &lt;td&gt;Print the total number of input lines&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk 'END { print NR }' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print the first 10 input lines&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk 'NR &amp;lt;= 10' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print the tenth input line&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk 'NR == 10' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print every tenth input line, starting with line 1&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk 'NR % 10 == 1' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print the last field of every input line&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk '{ print $NF }' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print the last field of the last input line&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk 'END { print $NF }' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print every input line with more than four fields&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk 'NF &amp;gt; 4' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print every input line that does not have exactly four fields&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk 'NF != 4' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print every input line in which the last field is greater than 4&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk '$NF &amp;gt; 4' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print the total number of fields in all input lines&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk '{ nf += NF } END { print nf }' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print the total number of lines that contain `Beth`&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk '/Beth/ { nlines++ } END { print nlines }' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print the largest field and the line that contains it&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk '$1 &amp;gt; max { max = $1; maxline = $0 } END { print max, maxline }' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print every line that has at least one field&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk 'NF &amp;gt; 0' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print every line longer than 80 characters&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk 'length($0) &amp;gt; 80' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print the number of fields in every line followed by the line itself&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk '{ print NF, $0 }' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print the first two fields, in opposite order, of every line&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk '{ print $2, $1 }' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Interchange the first two fields of every line and then print the line&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk '{ temp = $1; $1 = $2; $2 = temp; print }' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print every line preceded by its line number&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk '{ print NR, $0 }' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print every line with the first field replaced by the line number&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk '{ $1 = NR; print }' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print every line after erasing the second field&lt;/td&gt;
                &lt;td&gt;&lt;code&gt;awk '{ $2 = ""; print }' emp.data&lt;/code&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print in reverse order the fields of every line&lt;/td&gt;
                &lt;td&gt;
    &lt;pre&gt;awk '{
        for (i = NF; i &amp;gt; 0; i--) printf("%s ", $i)
        printf("\n")
    }' emp.data&lt;/pre&gt;
                &lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print the sums of the fields of every line&lt;/td&gt;
                &lt;td&gt;
    &lt;pre&gt;awk '{
        sum = 0
        for (i = 1; i &amp;lt;= NF; i++) sum = sum + $i
        print sum
    }' emp.data&lt;/pre&gt;
                &lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Add up all fields in all lines and print the sum&lt;/td&gt;
                &lt;td&gt;
    &lt;pre&gt;awk '
        { for (i = 1; i &amp;lt;= NF; i++) sum = sum + $i }
    END { print sum }
    ' emp.data&lt;/pre&gt;
                &lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td&gt;Print every line after replacing each field by its absolute value&lt;/td&gt;
                &lt;td&gt;
    &lt;pre&gt;awk '{
        for (i = 1; i &amp;lt;= NF; i++) if ($i &amp;lt; 0) $i = -$i
        print
    }' emp.data&lt;/pre&gt;
                &lt;/td&gt;
            &lt;/tr&gt;
        &lt;/tbody&gt;
    &lt;/table&gt;
&lt;/figure&gt;

&lt;h2 id="passing_parameters_to_awk_argc_and_argv"&gt;Passing parameters to Awk (&lt;code&gt;ARGC&lt;/code&gt; and &lt;code&gt;ARGV&lt;/code&gt;)&lt;/h2&gt;

&lt;p&gt;Built-ins variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ARGC&lt;/code&gt; → number of arguments&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ARGV&lt;/code&gt; → array of arguments
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ARGV[1]&lt;/code&gt; → first argument&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ARGV[ARGC-1]&lt;/code&gt; → last argument&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ARGV[0]&lt;/code&gt; → name of the program (usually &lt;code&gt;awk&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- BR --&gt;

&lt;pre&gt;&lt;code&gt;awk 'BEGIN { argv1 = ARGV[1] ; print argv1 }' foo
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Using arguments in a script file&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;a program stored in a file (&lt;code&gt;$*&lt;/code&gt; is the shell notation for &lt;a href="https://unix.stackexchange.com/q/141287"&gt;all the parameters to the script or function&lt;/a&gt;):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# progfile


awk 'BEGIN {
    argv0 = ARGV[0]
    argv1 = ARGV[1]
    argv2 = ARGV[2]
    print argv0, argv1, argv2
}' $*
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;make the file executable:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ chmod +x progfile
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;then call the program:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ ./progfile foo bar
awk foo bar
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="awk_for_exploratory_data_analysis"&gt;Awk for exploratory data analysis (EDA)&lt;/h2&gt;

&lt;p&gt;The purpose of exploratory data is to get a sense of what the data is, looking for both patterns and anomalies.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Be approximately right rather than exactly wrong. — &lt;a href="https://www.goodreads.com/author/quotes/5521280.John_W_Tukey"&gt;John W. Tukey&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Awk is great for quickly inspecting data.&lt;/p&gt;

&lt;h3 id="check_data_format"&gt;Check data format&lt;/h3&gt;

&lt;p&gt;Validate field structure, each line should have 5 fields and the total field should be correct:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk 'NF != 5 || $3 != $4 + $5' programs/titanic.tsv
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Ensure all records have the same number of fields:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk --csv '{ print NF }' programs/passengers.csv | sort | uniq -c | sort -nr
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="csv_dataset"&gt;CSV dataset&lt;/h3&gt;

&lt;p&gt;CSV is not rigorously defined, but commonly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fields containing &lt;strong&gt;commas&lt;/strong&gt; or &lt;strong&gt;quotes&lt;/strong&gt; must be enclosed in double quotes&lt;/li&gt;
&lt;li&gt;any field may be surrounded by quotes (whether it contains commas and quotes or not)&lt;/li&gt;
&lt;li&gt;an empty field is just &lt;code&gt;""&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;quotes inside a field are doubled: &lt;code&gt;""","""&lt;/code&gt; represents &lt;code&gt;","&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;fields may contain newline characters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Awk ≥ 2023 support the &lt;code&gt;--csv&lt;/code&gt; argument which causes input lines to be split into fields according to this rule:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk --csv 'NR &amp;gt; 1 { print $2 }' programs/passengers.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="generating_csv"&gt;Generating CSV&lt;/h3&gt;

&lt;p&gt;Double each quote and surround the result with quotes:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# to_csv - convert to proper "..."

function to_csv(s) {
    gsub(/"/, "\"\"", s)
    return "\"" s "\""
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This function can be used within a loop to insert commas between fields:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '

    function to_csv(s) {
        gsub(/"/, "\"\"", s)
        return "\"" s "\""
    }

    function record_to_csv(     s, i) {
        for (i = 1; i &amp;lt; NF; i++) {
            s = s to_csv($i) ","
        }
        s = s to_csv($NF)  # No comma after the last field `$NF`.
        return s
    }

    {
        print record_to_csv()
    }

' data/emp.data
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Or for an array:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;function array_to_csv(arr,       s, i, n) {
    n = length(arr)
    for (i = 1; i &amp;lt;= n; i++) {
        s = s to_csv(arr[i]) ","
    }
    return substr(s, 1, length(s) - 1)  # Remove trailing comma.
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="generating_tsv"&gt;Generating TSV&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;awk 'NR &amp;gt; 1 { OFS="\t" ; print $1, $2, $3}' data/emp.data &amp;gt; ~/Desktop/foo.tsv
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="associative_arrays"&gt;Associative arrays&lt;/h3&gt;

&lt;p&gt;Awk supports associative arrays:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the subscripts (or indices) of Awk arrays can be arbitrary strings:
&lt;ul&gt;
&lt;li&gt;arrays that allow arbitrary strings as subscripts are called associative arrays&lt;/li&gt;
&lt;li&gt;other languages provide the same facility with dictionary, map or hashmap&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Awk has a special form of the &lt;code&gt;for&lt;/code&gt; statement for iterating over the indices of an associative array:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;for (i in array) { statements }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the element of the array are visited in an unspecified order&lt;/li&gt;
&lt;li&gt;you can't count on any particular order&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How many people are there in each category?&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '
    NR &amp;gt; 1 { types[$1] += $3 ; classes[$2] += $3 }
    END {
        for (type in types) {
            print type, types[type]
        }
        print ""
        for (class in classes) {
            print class, classes[class]
        }
    }
' programs/titanic.tsv
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Compute the survival rate for each category and pipe the result to &lt;code&gt;sort&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk 'NR &amp;gt; 1 { printf("%6s %6s %6.1f%%\n", $1, $2, 100 * $4 / $3) }' programs/titanic.tsv | sort -k3 -nr
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="beer_reviews_dataset"&gt;Beer reviews dataset&lt;/h3&gt;

&lt;p&gt;Dataset source: &lt;a href="https://www.kaggle.com/datasets/rdoume/beerreviews"&gt;kaggle&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;File size:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;wc data/beer_reviews.csv  # Number of lines, words, and bytes.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Awk equivalent:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk '
    { nc += length($0) + 1 ; words_num += NF }
    END {
        print NR,  # Lines.
        words_num,
        nc,
        FILENAME
    }
' data/beer_reviews.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Fields of the CSV:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;brewery_id&lt;/li&gt;
&lt;li&gt;&lt;mark&gt;brewery_name&lt;/mark&gt;&lt;/li&gt;
&lt;li&gt;review_time&lt;/li&gt;
&lt;li&gt;&lt;mark&gt;review_overall&lt;/mark&gt;&lt;/li&gt;
&lt;li&gt;review_aroma&lt;/li&gt;
&lt;li&gt;review_appearance&lt;/li&gt;
&lt;li&gt;review_profilename&lt;/li&gt;
&lt;li&gt;&lt;mark&gt;beer_style&lt;/mark&gt;&lt;/li&gt;
&lt;li&gt;review_palate&lt;/li&gt;
&lt;li&gt;review_taste&lt;/li&gt;
&lt;li&gt;&lt;mark&gt;beer_name&lt;/mark&gt;&lt;/li&gt;
&lt;li&gt;&lt;mark&gt;beer_abv&lt;/mark&gt; (alcohol content, percentage of alcohol by volume or ABV)&lt;/li&gt;
&lt;li&gt;beer_beerid&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Find the strongest beer:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk --csv '
    NR &amp;gt; 1 &amp;amp;&amp;amp; $12 &amp;gt; max_abv { max_abv = $12 ; brewery = $2 ; name = $11 }
    END { print max_abv, brewery, name }
' data/beer_reviews.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The result is surprisingly high.&lt;/p&gt;

&lt;p&gt;That raises a follow-up question: is this value an outlier or the tip of a substantial alcoholic iceberg:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk --csv 'NR &amp;gt; 1 &amp;amp;&amp;amp; $12 &amp;gt;= 10 { print $2, $11, $12 }' data/beer_reviews.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;What about low-alcohol beer?&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;awk --csv 'NR &amp;gt; 1 &amp;amp;&amp;amp; $12 &amp;lt;= 0.5 &amp;amp;&amp;amp; $12 &amp;gt; 0 { print $2, $11, $12 }' data/beer_reviews.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;What ratings are associated with high and low alcohol?&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# Rating of high alcohol.
awk --csv '
    $12 &amp;gt;= 10 { rate += $4 ; nrate++ }
    END { print rate / nrate, nrate }
' data/beer_reviews.csv

# Rating of low alcohol.
awk --csv '
    $12 &amp;lt;= 0.5 &amp;amp;&amp;amp; $12 &amp;gt; 0 { rate += $4 ; nrate++ }
    END { print rate / nrate, nrate }
' data/beer_reviews.csv

# Average rating.
awk --csv '
    $12 &amp;gt; 0 { rate += $4 ; nrate++ }
    END { print rate / nrate, nrate }
' data/beer_reviews.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We use &lt;code&gt;$12 &amp;gt; 0&lt;/code&gt; because some fields don't have a ABV.&lt;/p&gt;

&lt;p&gt;When exploring data, always check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how many fields are empty?&lt;/li&gt;
&lt;li&gt;how many fields have an explicitely non-useful value like "N/A"?&lt;/li&gt;
&lt;li&gt;what is the range of values in a column (min/max)?&lt;/li&gt;
&lt;li&gt;what are the distinct values?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automating these checks with small scripts can save a lot of time.&lt;/p&gt;</description><pubDate>Sun, 19 Apr 2026 06:06:30 +0000</pubDate><guid>https://marcarea.com/weblog/2026/04/19/awk</guid></item><item><title>Command-line skills</title><link>https://marcarea.com/weblog/2026/04/18/command-line-skills</link><description>&lt;p&gt;&lt;small&gt;This post is part of a three-part series: &lt;a href="/weblog/2026/04/17/shell-fundamentals-with-bash" title="Shell Fundamentals with Bash"&gt;#1&lt;/a&gt;, &lt;a href="/weblog/2026/04/18/ways-to-run-a-shell-command" title="Ways to run a Shell Command"&gt;#2&lt;/a&gt;, &lt;a href="/weblog/2026/04/18/command-line-skills" title="Command-Line Skills"&gt;#3&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;The rest of my notes from &lt;a href="https://efficientlinux.com/"&gt;&lt;em&gt;Efficient Linux at the Command Line&lt;/em&gt;&lt;/a&gt; (2022) by &lt;a href="https://danieljbarrett.com/"&gt;Daniel J. Barrett&lt;/a&gt;, focused on a handful of simple tricks that make a real difference.&lt;/p&gt;

&lt;h2 id="combining_commands_with_pipes"&gt;Combining commands with pipes&lt;/h2&gt;

&lt;h3 id="less"&gt;&lt;code&gt;less&lt;/code&gt;&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;tree .git/objects/ | less
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="wc"&gt;&lt;code&gt;wc&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Count lines in each file:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;wc -l *
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;How many files/directories?&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ls -1 | wc -l
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Why &lt;code&gt;ls -1&lt;/code&gt;? Because &lt;code&gt;ls&lt;/code&gt; changes its behavior when redirected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unlike other commands &lt;code&gt;ls&lt;/code&gt; is aware of whether &lt;code&gt;stdout&lt;/code&gt; is the screen or whether it's been redirected (to a pipe or otherwise):
&lt;ul&gt;
&lt;li&gt;when &lt;code&gt;stdout&lt;/code&gt; is the screen, &lt;code&gt;ls&lt;/code&gt; arranges its output in multiple columns (the reason is user friendliness)&lt;/li&gt;
&lt;li&gt;when &lt;code&gt;ls&lt;/code&gt; is redirected, it produces a single column&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ls -1&lt;/code&gt; forces a single column output
&lt;ul&gt;
&lt;li&gt;it is not strictly necessary when redirected but it's more explicit&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="head"&gt;&lt;code&gt;head&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;head&lt;/code&gt; prints the first lines of a file.&lt;/p&gt;

&lt;p&gt;Efficient for large files.&lt;/p&gt;

&lt;p&gt;Can be used to reduce the output from another command:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ls -1 /bin | head -n5
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="cut"&gt;&lt;code&gt;cut&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;cut&lt;/code&gt; prints one or more column from a file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cut&lt;/code&gt; by field (&lt;code&gt;-f&lt;/code&gt;) when the input consist of strings (fields) separated by &lt;code&gt;tab&lt;/code&gt; characters:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cut -f2 animals.txt
cut -f2,3 animals.txt  # Multiple fields.
cut -f2-4 animals.txt  # By numeric range.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Or &lt;code&gt;cut&lt;/code&gt; by character position (&lt;code&gt;-c&lt;/code&gt;):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cut -c1-3 animals.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Use a custom delimiter (&lt;code&gt;-d&lt;/code&gt;) to &lt;code&gt;cut&lt;/code&gt; a CSV…&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cut -d";" -f2 data.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;…and re-pipe to &lt;code&gt;cut&lt;/code&gt; to get &lt;code&gt;FirstName&lt;/code&gt; from &lt;code&gt;LastName FirstName&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cut -d";" -f5 data.csv | cut -d" " -f2 | head -n10
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="sort"&gt;&lt;code&gt;sort&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;sort&lt;/code&gt; sorts alphabetically (default) or numerically (&lt;code&gt;-n&lt;/code&gt;) in ascending (default) or descending (&lt;code&gt;-r&lt;/code&gt;) order:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sort animals.txt
sort -r animals.txt

cut -f3 animals.txt | sort -n
cut -f3 animals.txt | sort -rn
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Get min and max:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cut -f3 animals.txt | sort -n | head -n1   # min.
cut -f3 animals.txt | sort -rn | head -n1  # max.
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="uniq"&gt;&lt;code&gt;uniq&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;uniq&lt;/code&gt; detect repeated "&lt;strong&gt;adjacent&lt;/strong&gt;" lines in a file. It does not scan the entire file for duplicates unless the duplicates are next to each other.&lt;/p&gt;

&lt;p&gt;By default, it removes the repeats if any:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;uniq animals.txt
uniq -c animals.txt

# grades.txt contains alphabetical grades (A, B, C…)
cut -f1 grades.txt | sort | uniq -c | sort -nr
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="testing_tools"&gt;Testing tools&lt;/h3&gt;

&lt;p&gt;Combining commands with pipes often requires trial and error.&lt;/p&gt;

&lt;p&gt;Tools and techniques to help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ls&lt;/code&gt; or &lt;code&gt;echo&lt;/code&gt; to test destructive commands (e.g., &lt;code&gt;echo&lt;/code&gt; instead of &lt;code&gt;rm&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tee&lt;/code&gt; to view intermediate results:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;command1 | command2 | command3 | tee outfile | command4 | command5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tee&lt;/code&gt; saves the output from &lt;code&gt;command3&lt;/code&gt; in &lt;code&gt;outfile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;while piping the same output to &lt;code&gt;command4&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="producing_text"&gt;Producing text&lt;/h2&gt;

&lt;h3 id="date"&gt;&lt;code&gt;date&lt;/code&gt;&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;date
date +%Y-%m-%d
date +%H:%M:%S
date +%Y-%m-%d\ %H:%M:%S
date +"I cannot believe it's already %A"
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="seq_sequence_of_numbers"&gt;&lt;code&gt;seq&lt;/code&gt; (sequence of numbers)&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;seq 1 5
seq 1 2 10        # Middle number = increment.
seq 3 -1 0
seq 1.1 0.1 2
seq -s/ 1 5       # --separator
seq -w 8 10       # --fixed-width
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;BSD alternative: &lt;a href="https://leancrew.com/all-this/2019/09/making-lists-with-jot-and-seq/"&gt;&lt;code&gt;jot&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id="brace_expansion"&gt;&lt;em&gt;Brace expansion&lt;/em&gt;&lt;/h3&gt;

&lt;p&gt;Or &lt;a href="https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html"&gt;&lt;em&gt;curly brace expansion&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{x..y..z}&lt;/code&gt; generates the values &lt;code&gt;x&lt;/code&gt; through &lt;code&gt;y&lt;/code&gt; incrementing by &lt;code&gt;z&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;echo {1..10}
echo {10..1}
echo {01..10}

echo {A..Z}
echo {A..Z} | tr -d " "  # Delete spaces.
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="find"&gt;&lt;code&gt;find&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;List files in a directory recursively.&lt;/p&gt;

&lt;p&gt;The default action is &lt;code&gt;-print&lt;/code&gt; (it can be omitted it in the following commands):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;find /etc -print    # List all of /etc recursively.

find . -type f -print
find . -type d -print

find . -type f -name "*.py" -print

find . -iname "*txt" -print
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Execute a command for each file path with &lt;code&gt;-exec&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;construct a &lt;code&gt;find&lt;/code&gt; command&lt;/li&gt;
&lt;li&gt;append &lt;code&gt;-exec&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;followed by the command to execute&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{}&lt;/code&gt; indicates where the file path should appear in the command&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;end with a quoted or escaped semicolon (&lt;code&gt;";"&lt;/code&gt; or &lt;code&gt;\;&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;E.g.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# Print an @ symbol on either side of the file path.
find . -exec echo @ {} @ ";"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;-exec&lt;/code&gt; is often used to &lt;code&gt;ls&lt;/code&gt; or &lt;code&gt;rm&lt;/code&gt; files:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# Delete files with names ending in a tilde within the directory and its subdirectories.
# Use `echo` for safety.
find /tmp -type f -name "*~" -exec echo rm {} ";"
# Delete for real.
find /tmp -type f -name "*~" -exec rm {} ";"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But some built-in actions are more efficient than &lt;code&gt;-exec ls&lt;/code&gt; or &lt;code&gt;-exec rm&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;find . -type f -name "*.txt" -ls

find /tmp -type f -name "*~" -delete
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="yes"&gt;&lt;code&gt;yes&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Prints the same string until terminated:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;yes woof!
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The main use for our purpose is printing a string a specific number of times:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;yes woof! | head -n3
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="isolating_text"&gt;Isolating text&lt;/h2&gt;

&lt;h3 id="grep"&gt;&lt;code&gt;grep&lt;/code&gt;&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;# Whole words only.
grep --color -w enough cite.txt

# Only nonblank lines with `-v` (--invert-match).
grep --invert-match "^$" cite.txt

# Literal matches only. Ignore regular expressions with `-F` ("fixed").
grep -F --color "e." cite.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="tail"&gt;&lt;code&gt;tail&lt;/code&gt;&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;tail -n3 cite.txt    # Last 3 lines.

tail -n+90 cite.txt  # From line 90 to the end.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Combine &lt;code&gt;tail&lt;/code&gt; and &lt;code&gt;head&lt;/code&gt; to print the fourth line only:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;head -n4 cite.txt | tail -n1
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="awk_print"&gt;&lt;code&gt;awk '{print}'&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;awk&lt;/code&gt; extracts columns in a way &lt;code&gt;cut&lt;/code&gt; cannot.&lt;/p&gt;

&lt;p&gt;Key concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;$2&lt;/code&gt;, etc. → columns&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$NF&lt;/code&gt; → last column ("number of fields")&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$0&lt;/code&gt; → entire line&lt;/li&gt;
&lt;li&gt;field separator → any run of spaces/tabs/newlines (vs a single character in &lt;code&gt;cut&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;awk&lt;/code&gt; does not print whitespaces values by default, use commas to add whitespace:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;df | awk '{print $1 $3}'    # No whitespace.

df | awk '{print $1, $3}'   # Whitespace.
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="combining_text"&gt;Combining text&lt;/h2&gt;

&lt;h3 id="cat"&gt;&lt;code&gt;cat&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;cat&lt;/code&gt; (con&lt;code&gt;cat&lt;/code&gt;enate) prints the output of multiple files to stdout:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cat poem1 poem2 poem3
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="tac"&gt;&lt;code&gt;tac&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;tac&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cat&lt;/code&gt; spelled backward&lt;/li&gt;
&lt;li&gt;reverses a file line by line&lt;/li&gt;
&lt;li&gt;great for processing data that is already in chronological order but not reversible with &lt;code&gt;sort -r&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;E.g. reverse a web-server log file to process its lines from newest to oldest:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;tac access.log
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="paste"&gt;&lt;code&gt;paste&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;paste&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;combines files side by side in columns separated by a single tab character&lt;/li&gt;
&lt;li&gt;good partner to &lt;code&gt;cut&lt;/code&gt; (which extracts columns from a tab separated file)&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- BR --&gt;

&lt;pre&gt;&lt;code&gt;paste words1.txt words2.txt | cut -f2

paste -d, words1.txt words2.txt         # Change separator.

paste -s words1.txt words2.txt          # Produce rows instead of columns.

paste -d"\n" words1.txt words2.txt      # Interleave data with a new line as separator.
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="diff"&gt;&lt;code&gt;diff&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;diff&lt;/code&gt; can be used as text processor that interleaves lines from two files.&lt;/p&gt;

&lt;p&gt;Many users don't think of &lt;code&gt;diff&lt;/code&gt; in this way but it can be used to solve certain problems.&lt;/p&gt;

&lt;p&gt;Isolate differing lines:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;diff file1.txt file2.txt

diff file1.txt file2.txt | grep '^[&amp;lt;&amp;gt;]'

diff file1.txt file2.txt | grep '^[&amp;lt;&amp;gt;]' | cut -c3-
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="transforming_text"&gt;Transforming text&lt;/h2&gt;

&lt;h3 id="tr"&gt;&lt;code&gt;tr&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;tr&lt;/code&gt; translates one set of characters into another:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;echo efficient | tr a-z A-Z

echo Efficient Linux | tr " " "\n"      # Convert spaces into new lines.

echo Efficient Linux | tr -d " \t"      # Delete spaces and tabs.
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="rev"&gt;&lt;code&gt;rev&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;rev&lt;/code&gt; reverses the character of each line of input:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;echo Efficient Linux | rev
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Beyond the obvious entertainment value, &lt;code&gt;rev&lt;/code&gt; can be used to extract the final word of each line:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;rev file1.txt | cut -d' ' -f1 | rev
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="sed_essentials"&gt;&lt;code&gt;sed&lt;/code&gt; essentials&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;sed&lt;/code&gt; uses a &lt;em&gt;sed script&lt;/em&gt; (a sequence of instructions) to transform text from &lt;code&gt;stdin&lt;/code&gt; into any other text.&lt;/p&gt;

&lt;p&gt;The most common type of script is a substitution script:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;echo Efficient Linux | sed s/Linux/macOS/

# The forward slashes may be replaced by any other character.
echo Efficient Linux | sed s@Linux@macOS@
echo Efficient Linux | sed s+Linux+macOS+
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Another type of script is a deletion script:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;seq 10 20 | sed 4d               # Remove the 4th line.

seq 101 200 | sed '/[13579]$/d'  # Delete lines ending with an odd digit.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Delete carriage returns and newlines:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sed -e s/$'\r'/,/g a.txt &amp;gt; b.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="practical_cli_tips"&gt;Practical CLI tips&lt;/h2&gt;

&lt;h3 id="launching_browser_from_the_cli"&gt;Launching browser from the CLI&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;open &amp;lt;url&amp;gt;  # Default browser.

open -a Safari
open -a Safari "https://marcarea.com"

open -a "Google Chrome"
open -a "Google Chrome" "https://marcarea.com"
open -na "Google Chrome" --args -incognito "https://marcarea.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="retrieving_rendered_web_content_with_a_text_based_browser"&gt;Retrieving rendered web content with a text-based browser&lt;/h3&gt;

&lt;p&gt;Use a text-based brower such as &lt;code&gt;lynx&lt;/code&gt; or &lt;code&gt;links&lt;/code&gt; to download a rendered page with the &lt;code&gt;-dump&lt;/code&gt; option:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;lynx -dump https://marcarea.com &amp;gt; tmpfile
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;useful for checking out a suspicous-looking link&lt;/li&gt;
&lt;li&gt;they can't promise complete security, so use your best judgement&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="clipboard_control"&gt;Clipboard control&lt;/h3&gt;

&lt;p&gt;On macOS, &lt;code&gt;pbcopy&lt;/code&gt; and &lt;code&gt;pbpaste&lt;/code&gt; connect selections to &lt;code&gt;stdin&lt;/code&gt; and &lt;code&gt;stdout&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;pbcopy &amp;lt; 00-intro.txt

pbpaste | wc -w

pbcopy &amp;lt; ~/.ssh/id_rsa.pub
pbpaste &amp;gt; main.go
pbpaste &amp;gt;&amp;gt; main.go

# Encode clipboard in base64.
pbpaste | base64 | pbcopy

# Copy external IP address to clipboard.
curl -Ss https://icanhazip.com | pbcopy

# Copy private IP address to clipboard.
ipconfig getifaddr en0 | pbcopy
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="editing_files_that_contains_a_given_string"&gt;Editing files that contains a given string&lt;/h3&gt;

&lt;p&gt;Open files containing a string in an editor:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;vi $(grep -l string *)

mate $(grep -l string *)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Recursively with the &lt;code&gt;-r&lt;/code&gt; option and beginning in the current directory (the dot):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mate $(grep -lr string .)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For faster search of large directory trees:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mate $(find . -type f -print0 | xargs -0 grep -l string)
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="processing_a_file_one_line_at_a_time"&gt;Processing a file one line at a time&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;cat&lt;/code&gt; the file into a &lt;code&gt;while read&lt;/code&gt; loop:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cat myfile | while read line; do
    echo "$line" | wc -c
done
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="generating_test_files"&gt;Generating test files&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;select a random number of words from a file using &lt;code&gt;$RANDOM&lt;/code&gt; (a positive integer between &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;32,767&lt;/code&gt;):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;gshuf -n $RANDOM /usr/share/dict/words
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;create a directory for files:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mkdir -p ~/Desktop/randomfiles &amp;amp;&amp;amp; cd ~/Desktop/randomfiles
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;generate a file with a random filename containing a random number of random words:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# Generate a random string.
openssl rand -hex 5


gshuf -n $RANDOM -o $(openssl rand -hex 5).txt /usr/share/dict/words
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;run the solution multiple times:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;with a for loop:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;for i in {1..10}; do
    gshuf -n $RANDOM -o $(openssl rand -hex 5).txt /usr/share/dict/words
done
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;or a one-liner (e.g., run the command 10 times using &lt;code&gt;yes&lt;/code&gt;):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;yes 'gshuf -n $RANDOM -o $(openssl rand -hex 5).txt /usr/share/dict/words' | head -n 10 | bash
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id="longer_learning"&gt;Longer learning&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cron&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;crontab&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;at&lt;/code&gt; (schedules commands to run once)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://linuxconfig.org/rsync-command-examples"&gt;&lt;code&gt;rsync&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://makefiletutorial.com/"&gt;&lt;code&gt;make&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><pubDate>Sat, 18 Apr 2026 15:06:23 +0000</pubDate><guid>https://marcarea.com/weblog/2026/04/18/command-line-skills</guid></item><item><title>Ways to run a shell command</title><link>https://marcarea.com/weblog/2026/04/18/ways-to-run-a-shell-command</link><description>&lt;p&gt;&lt;small&gt;This post is part of a three-part series: &lt;a href="/weblog/2026/04/17/shell-fundamentals-with-bash" title="Shell Fundamentals with Bash"&gt;#1&lt;/a&gt;, &lt;a href="/weblog/2026/04/18/ways-to-run-a-shell-command" title="Ways to run a Shell Command"&gt;#2&lt;/a&gt;, &lt;a href="/weblog/2026/04/18/command-line-skills" title="Command-Line Skills"&gt;#3&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;There are several ways to run a shell command.&lt;/p&gt;

&lt;p&gt;This post summarizes Chapter 7 of &lt;a href="https://efficientlinux.com/"&gt;&lt;em&gt;Efficient Linux at the Command Line&lt;/em&gt;&lt;/a&gt; (2022) by &lt;a href="https://danieljbarrett.com/"&gt;Daniel J. Barrett&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="conditional_lists"&gt;Conditional lists (&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, &lt;code&gt;||&lt;/code&gt;)&lt;/h2&gt;

&lt;p&gt;Run commands based on success or failure:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cd dir &amp;amp;&amp;amp; touch new.txt  # Run 2nd command if 1st succeeds.
cd dir || mkdir          # Run 2nd command only if the first fails.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Combine both:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(cd dir || mkdir dir) &amp;amp;&amp;amp; cd dir || echo "I failed"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Exit codes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every command returns an &lt;strong&gt;exit code&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt; → success&lt;/li&gt;
&lt;li&gt;non-zero value → failure&lt;/li&gt;
&lt;li&gt;to check the exit code of most recent command: &lt;code&gt;echo $?&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="unconditional_lists"&gt;Unconditional lists (&lt;code&gt;;&lt;/code&gt;)&lt;/h2&gt;

&lt;p&gt;Run commands in sequence (ignore success/failure):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sleep 5; cp -a ~/important-files /mnt/backup_drive
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="command_substitution"&gt;Command substitution (&lt;code&gt;$()&lt;/code&gt;,&lt;code&gt;``&lt;/code&gt;)&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html"&gt;Command substitution&lt;/a&gt; inserts the output of one command into another:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mv $(grep -l "Artist: Kansas" *.txt) kansas
mv result1.txt result2.txt result3.txt kansas  # Result.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Older syntax with &lt;em&gt;backtits&lt;/em&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mv `grep -l "Artist: Kansas" *.txt` kansas
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;$()&lt;/code&gt; is simpler to nest:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;echo Today is $(echo $(gdate +%A) | tr a-z A-Z)!
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Command substitution runs the command in a &lt;strong&gt;subshell&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id="process_substitution"&gt;Process substitution (&lt;code&gt;&amp;lt;()&lt;/code&gt;)&lt;/h2&gt;

&lt;p&gt;Process substitution treats command output like a file. It allows commands that read only from disk files (like &lt;code&gt;cat&lt;/code&gt; or &lt;code&gt;cp&lt;/code&gt;) to read from &lt;code&gt;stdin&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cat &amp;lt;(ls -1 *.jpg | sort -n)

cp &amp;lt;(ls -1 *.jpg | sort -n) ~/Desktop/list.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;diff &amp;lt;(ls -1 *.jpg | sort -n) &amp;lt;(seq 1 1000 | sed 's/$/.jpg/')
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;you have files &lt;code&gt;1.jpg–1000.jpg&lt;/code&gt;, but some are missing&lt;/li&gt;
&lt;li&gt;to find them, compare existing filenames with the full list using &lt;code&gt;diff&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;because &lt;code&gt;diff&lt;/code&gt; needs files (not &lt;code&gt;stdin&lt;/code&gt;), use process substitution:
&lt;ul&gt;
&lt;li&gt;it runs the command in a subshell&lt;/li&gt;
&lt;li&gt;and exposes both lists as files to &lt;code&gt;diff&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="pass_commands_to_bash"&gt;Pass commands to bash (&lt;code&gt;-c&lt;/code&gt;)&lt;/h2&gt;

&lt;p&gt;Pass a command to bash as a string:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;bash -c "ls -l"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Useful to run whole commands as the superuser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo echo "New log file" &amp;gt; /var/log/custom.log&lt;/code&gt; fails:
&lt;ul&gt;
&lt;li&gt;after pressing &lt;code&gt;Enter&lt;/code&gt;, the shell processes &lt;code&gt;&amp;gt;&lt;/code&gt; first&lt;/li&gt;
&lt;li&gt;it tries to create the file without permission → "Permission denied"&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo&lt;/code&gt; never runs (it only applies to &lt;code&gt;echo&lt;/code&gt;, not the redirection)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;to solve this, run the whole command as root:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sudo bash -c 'echo "New log file" &amp;gt; /var/log/custom.log'
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="pipe_commands_to_bash_bash"&gt;Pipe commands to bash (&lt;code&gt;| bash&lt;/code&gt;)&lt;/h2&gt;

&lt;p&gt;You can pipe commands directly into bash:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;echo "ls -l" | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This enables a repeatable workflow for commands: generate → review → execute.&lt;/p&gt;

&lt;p&gt;E.g. batch rename files by adding a prefix:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# Check command with `less` first.
ls -1 *.txt | sed 's/.*/mv "&amp;amp;" "old_&amp;amp;"/' | less

# Execute after review.
ls -1 *.txt | sed 's/.*/mv "&amp;amp;" "old_&amp;amp;"/' | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="run_commands_remotely_ssh"&gt;Run commands remotely (&lt;code&gt;ssh&lt;/code&gt;)&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ssh&lt;/code&gt; can execute a single command on the remote host (quicker than logging in, running the command and logging out):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ssh myhost.example.com ls
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Control output redirection:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ssh myhost.example.com ls &amp;gt; outfile         # Creates outfile on local host.

ssh myhost.example.com "ls &amp;gt; outfile"       # Creates outfile on remote host.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Commands can be piped to &lt;code&gt;ssh&lt;/code&gt; to run them on the remote host:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;echo "ls &amp;gt; outfile" | ssh myhost.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you see message about pseudo-terminals or pseudo-ttys, use &lt;code&gt;-T&lt;/code&gt; to prevent the SSH server from allocating a terminal:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;echo "ls &amp;gt; outfile" | ssh -T myhost.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you see message like "Welcome to Linux!" then tell &lt;code&gt;ssh&lt;/code&gt; explicitely to run &lt;code&gt;bash&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;echo "ls &amp;gt; outfile" | ssh myhost.example.com bash
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="run_a_list_of_commands_xargs"&gt;Run a list of commands (&lt;code&gt;xargs&lt;/code&gt;)&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;xargs&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;takes input from &lt;code&gt;stdin&lt;/code&gt; as &lt;strong&gt;input strings&lt;/strong&gt; separated by whitespace&lt;/li&gt;
&lt;li&gt;turns it into arguments for a &lt;strong&gt;command template&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any command can run recursively by combining &lt;code&gt;find&lt;/code&gt; and &lt;code&gt;xargs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example, count lines in all Python files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pipe the file listing to &lt;code&gt;xargs&lt;/code&gt; to serve as its &lt;strong&gt;input strings&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and provide &lt;code&gt;wc -l&lt;/code&gt; to serve as the &lt;strong&gt;command template&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;find . -type f -name \*.py -print0 | xargs -0 wc -l
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Safety with &lt;code&gt;find&lt;/code&gt; and &lt;code&gt;xargs&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;xargs -0&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;by default, &lt;code&gt;xargs&lt;/code&gt; splits input on whitespace (spaces, tabs, newlines)&lt;/li&gt;
&lt;li&gt;this becomes a problem when &lt;em&gt;input strings&lt;/em&gt; (e.g. filenames) contain whitespace → one filename can be interpreted as multiple arguments&lt;/li&gt;
&lt;li&gt;so &lt;code&gt;xargs -0&lt;/code&gt; tells &lt;code&gt;xargs&lt;/code&gt; to use the &lt;code&gt;null&lt;/code&gt; character (ASCII zero) as the separator instead&lt;/li&gt;
&lt;li&gt;since &lt;code&gt;\0&lt;/code&gt; cannot appear in filenames, this makes it safe&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;find -print0&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;separates &lt;em&gt;input strings&lt;/em&gt; by &lt;code&gt;null&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;note that &lt;code&gt;ls&lt;/code&gt; can't do that:
&lt;ul&gt;
&lt;li&gt;but we can convert newlines to &lt;code&gt;null&lt;/code&gt;s with &lt;code&gt;tr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ls | tr '\n' '\0' | xargs -0 …&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most important &lt;code&gt;xargs&lt;/code&gt; options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;-n&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;controls how many arguments are appended by &lt;code&gt;xargs&lt;/code&gt; onto each generated command&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the default is to append as many arguments as will fit the shell's limits:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ls | xargs echo
ls | xargs -n1 echo    # One argument per echo command.
ls | xargs -n2 echo    # Two arguments per echo command.
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;-I&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;controls where the &lt;em&gt;input strings&lt;/em&gt; appear in the generated command&lt;/li&gt;
&lt;li&gt;&lt;p&gt;follow &lt;code&gt;-I&lt;/code&gt; whith any string which becomes a placeholder for &lt;em&gt;input strings&lt;/em&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ls -1 | xargs -I FOO echo FOO rocks
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;note: &lt;code&gt;-I&lt;/code&gt; limits &lt;code&gt;xargs&lt;/code&gt; to one input string per generated command&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;xargs&lt;/code&gt; is a problem solver when commands lines grow very long:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# May fail when evaluating a string &amp;gt; 14 million characters: "Argument list too long".
rm *.txt

# Use `xargs` to split the list of files accross multiple `rm` commands.
find . -maxdepth 1 -name \.txt -type f -print0 | xargs -0 rm
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="run_a_command_in_the_background"&gt;Run a command in the background (&lt;code&gt;&amp;amp;&lt;/code&gt;)&lt;/h2&gt;

&lt;p&gt;Run a command in the background with an &lt;em&gt;ampersand&lt;/em&gt; &lt;code&gt;&amp;amp;&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sleep 20 &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;the output may appear (a) at any time or (b) not until the next time we press enter&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the amperstand is also a list operator like &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;command1 &amp;amp; command2 &amp;amp; command3 &amp;amp;  # All 3 commands in the background.
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suspend + resume in the background:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sleep 20
Crtl-z    # Suspend the command.
bg        # Resume the command in the background.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Backgroung commands are part of a shell feature called &lt;strong&gt;job control&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;job&lt;/strong&gt; is a shell's unit of work with a job ID&lt;/li&gt;
&lt;li&gt;a job = one or more processes (e.g., a pipeline of 6 programs is a single job including 6 processes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Job control commands:&lt;/p&gt;

&lt;table&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th&gt;Command&lt;/th&gt;
            &lt;th&gt;Meaning&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td&gt;&lt;code&gt;jobs&lt;/code&gt;&lt;/td&gt;
            &lt;td&gt;list background jobs&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&lt;code&gt;bg&lt;/code&gt;&lt;/td&gt;
            &lt;td&gt;Move the current suspended job into the background&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&lt;code&gt;bg %n&lt;/code&gt;&lt;/td&gt;
            &lt;td&gt;Move suspended job number &lt;code&gt;n&lt;/code&gt; into the background (&lt;code&gt;bg %1&lt;/code&gt;)&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&lt;code&gt;fg&lt;/code&gt;&lt;/td&gt;
            &lt;td&gt;Move the current suspended job into the foreground&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&lt;code&gt;fg %n&lt;/code&gt;&lt;/td&gt;
            &lt;td&gt;Move background job number &lt;code&gt;n&lt;/code&gt; into the foreground (&lt;code&gt;fg %2&lt;/code&gt;)&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&lt;code&gt;kill %n&lt;/code&gt;&lt;/td&gt;
            &lt;td&gt;Terminate background job number &lt;code&gt;n&lt;/code&gt; (&lt;code&gt;kill %3&lt;/code&gt;)&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Screen output from job can appear at any time while the job runs. To avoid clutter, redirect stdout to a file:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sort /usr/share/dict/words | head -n2 &amp;gt; ~/Desktop/results.txt &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="explicit_subshells"&gt;Explicit subshells (&lt;code&gt;()&lt;/code&gt;)&lt;/h2&gt;

&lt;p&gt;To launch a subshell explicitely, enclose a command in parentheses:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(cd /usr/local &amp;amp;&amp;amp; ls)
pwd  # `cd /usr/local` occured in a subshell.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is useful, for example, when a pipeline needs to change directories mid-execution.&lt;/p&gt;

&lt;h2 id="process_replacement_exec"&gt;Process replacement (&lt;code&gt;exec&lt;/code&gt;)&lt;/h2&gt;

&lt;p&gt;When we run a command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the shell runs it in a separate process which will be destroyed when the command exits&lt;/li&gt;
&lt;li&gt;this behavior can be changed with the builtin &lt;code&gt;exec&lt;/code&gt; command&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;exec&lt;/code&gt; command &lt;strong&gt;replaces&lt;/strong&gt; the current shell process with another command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the shell does not create a new process&lt;/li&gt;
&lt;li&gt;instead, it becomes the new command&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the new command exits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no shell prompt will follow because the original shell is gone&lt;/li&gt;
&lt;li&gt;if you were in a terminal, it may close&lt;/li&gt;
&lt;li&gt;if it was a login shell, you may be logged out&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- BR --&gt;

&lt;pre&gt;&lt;code&gt;bash                # Run a child shell.
PS1="Doomed&amp;gt; "      # Change the new shell's prompt.
echo hello          # Run any command you like.
exec ls             # ls replaces the child shell, runs and exits.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Reasons to run &lt;code&gt;exec&lt;/code&gt; (mostly in shell scripts):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;save resources by not launching a second process
&lt;ul&gt;
&lt;li&gt;shell scripts use this optimization on the final command of the script&lt;/li&gt;
&lt;li&gt;if the script runs millions of times, the saving might be worth it&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;reassign &lt;code&gt;stdin&lt;/code&gt;, &lt;code&gt;stdout&lt;/code&gt; or &lt;code&gt;sderr&lt;/code&gt; for the current shell&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;this is a second ability of &lt;code&gt;exec&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;exec&lt;/code&gt; can redirect &lt;code&gt;stdout&lt;/code&gt; for an entire script instead of redirecting the output of each command&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#!/bin/bash
# Redirect stdout for this script.
exec &amp;gt; /tmp/outfile
# All subsequent commands print to /tmp/outfile


echo "My name is $USER"
echo "My current directory is $PWD"
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;</description><pubDate>Sat, 18 Apr 2026 15:06:10 +0000</pubDate><guid>https://marcarea.com/weblog/2026/04/18/ways-to-run-a-shell-command</guid></item></channel></rss>