<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Software Development on Brad's Blog</title><link>https://blog.bjdean.id.au/tags/software-development/</link><description>Recent content in Software Development on Brad's Blog</description><generator>Hugo -- 0.152.2</generator><language>en-au</language><copyright>Bradley Dean</copyright><lastBuildDate>Thu, 12 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.bjdean.id.au/tags/software-development/index.xml" rel="self" type="application/rss+xml"/><item><title>git bisect primer</title><link>https://blog.bjdean.id.au/2026/03/git-bisect-primer/</link><pubDate>Thu, 12 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.bjdean.id.au/2026/03/git-bisect-primer/</guid><description>&lt;p&gt;You&amp;rsquo;re staring at a bug that definitely wasn&amp;rsquo;t there six months ago. The codebase has hundreds of commits since then. You could read through every one looking for the culprit - or you could let &lt;a href="https://git-scm.com/docs/git-bisect"&gt;git bisect&lt;/a&gt; find it for you in five steps.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git bisect&lt;/code&gt; performs a binary search through your commit history. You tell it one good commit and one bad commit, and it checks out the midpoint for you to test. You say &amp;ldquo;good&amp;rdquo; or &amp;ldquo;bad&amp;rdquo;, it halves the range again (running a binary search) and in no time it tells you exactly which commit introduced the problem.&lt;/p&gt;</description></item><item><title>Building an Interactive HTTP/2 and HTTP/3 Client in Python</title><link>https://blog.bjdean.id.au/2026/01/interactive-http2-http3-client-python/</link><pubDate>Wed, 07 Jan 2026 11:19:25 +1100</pubDate><guid>https://blog.bjdean.id.au/2026/01/interactive-http2-http3-client-python/</guid><description>Exploring how to build interactive HTTP/2 and HTTP/3 clients in Python, similar to the telnet-style interaction possible with HTTP/1.1</description></item><item><title>The Longevity of Design Decisions: crontab -e / crontab -r</title><link>https://blog.bjdean.id.au/2025/12/longevity-design-decisions-crontab/</link><pubDate>Tue, 16 Dec 2025 15:35:59 +1100</pubDate><guid>https://blog.bjdean.id.au/2025/12/longevity-design-decisions-crontab/</guid><description>How a single UX decision in 1987 created a persistent design flaw that took years to partially address, and what this teaches us about the lasting impact of interface design choices</description></item><item><title>explain-strace: Making Strace Output Easier To Read</title><link>https://blog.bjdean.id.au/2025/11/explain-strace-making-strace-output-easier-to-read/</link><pubDate>Thu, 20 Nov 2025 15:14:24 +1100</pubDate><guid>https://blog.bjdean.id.au/2025/11/explain-strace-making-strace-output-easier-to-read/</guid><description>Building a tool to parse and explain strace output with human-readable descriptions</description></item><item><title>AI-Powered Spreadsheet Macro Security Analysis with Claude SDK</title><link>https://blog.bjdean.id.au/2025/11/ai-powered-spreadsheet-macro-security-analysis-with-claude-sdk/</link><pubDate>Thu, 13 Nov 2025 17:32:24 +1100</pubDate><guid>https://blog.bjdean.id.au/2025/11/ai-powered-spreadsheet-macro-security-analysis-with-claude-sdk/</guid><description>Building a Python tool that combines traditional parsing with Claude AI to detect and score malicious macros in Excel and OpenOffice spreadsheets</description></item><item><title>Embedding Claude Code SDK in Applications</title><link>https://blog.bjdean.id.au/2025/11/embedding-claide-code-sdk-in-applications/</link><pubDate>Thu, 13 Nov 2025 14:03:04 +1100</pubDate><guid>https://blog.bjdean.id.au/2025/11/embedding-claide-code-sdk-in-applications/</guid><description>An introduction to integrating the Claude Code SDK into your applications for production-ready AI agents with full tool access and context management</description></item><item><title>A 5-10 Minute Couch Vibe Code and a Fish Feeding Frenzy</title><link>https://blog.bjdean.id.au/2025/07/a-5-10-minute-couch-vibe-code-and-a-fish-feeding-frenzy/</link><pubDate>Thu, 31 Jul 2025 22:58:25 +0000</pubDate><guid>https://blog.bjdean.id.au/2025/07/a-5-10-minute-couch-vibe-code-and-a-fish-feeding-frenzy/</guid><description>&lt;p&gt;I built a browser-based fish feeding game using Claude AI assistance to explore vibe coding. Around ten minutes in I had two versions, nearly 1000 lines of HTML, CSS and javascript (the first two of which I know but don&amp;rsquo;t really enjoy coding) and some pretty interesting implementation details.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://bjdean.id.au/app/fish_feeding_game-v001.html"&gt;Version 1 is here&lt;/a&gt;. (Result of first basic prompt)&lt;/p&gt;
&lt;p&gt;&lt;a href="https://bjdean.id.au/app/fish_feeding_game-v002.html"&gt;Version 2 is here&lt;/a&gt;. (After asking for a reset button and some special effects)&lt;/p&gt;</description></item><item><title>AI Pair Programming - Learning Acceleration for Generalists</title><link>https://blog.bjdean.id.au/2025/06/ai-pair-programming-learning-acceleration-for-generalists/</link><pubDate>Sun, 22 Jun 2025 13:59:18 +0000</pubDate><guid>https://blog.bjdean.id.au/2025/06/ai-pair-programming-learning-acceleration-for-generalists/</guid><description>&lt;h2 id="tldr"&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;AI pair programming doesn&amp;rsquo;t just fill knowledge gaps - it accelerates learning across domains.&lt;/strong&gt; AI consistently explains context and reasoning, making you a better generalist faster. You&amp;rsquo;re not just getting answers; you&amp;rsquo;re building transferable mental models.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Human pair programming has an interesting quirk: experts often skip explaining the &amp;ldquo;obvious&amp;rdquo; parts. When Sarah shows you how to configure Kubernetes networking, she might breeze past CIDR notation because &amp;ldquo;everyone knows that.&amp;rdquo; When Jake walks through Django middleware, he assumes you understand decorators.&lt;/p&gt;</description></item><item><title>AI Pair Programming: On-call Subject Matter Experts for Generalist Developers</title><link>https://blog.bjdean.id.au/2025/06/ai-pair-programming-on-call-subject-matter-experts-for-generalist-developers/</link><pubDate>Sun, 22 Jun 2025 13:41:31 +0000</pubDate><guid>https://blog.bjdean.id.au/2025/06/ai-pair-programming-on-call-subject-matter-experts-for-generalist-developers/</guid><description>&lt;h2 id="tldr"&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;AI pair programming solves the generalist developer&amp;rsquo;s biggest challenge: context-switching overhead.&lt;/strong&gt; While you handle the core concepts, novel problem solving, and creative output, your AI assistant fills in the ancillary knowledge gaps for whatever technology you&amp;rsquo;re working with right now. Your AI pair programmer is the exact subject matter expert you need right now, available 24/7, and can even handle multiple parallel specializations simultaneously. It&amp;rsquo;s like having every team expert at your fingertips without the coordination overhead.&lt;/p&gt;</description></item><item><title>Deep Dive Series: Fine-Tuning (Small) Large Language Models on a PC</title><link>https://blog.bjdean.id.au/2025/06/deep-dive-series-fine-tuning-small-large-language-models-on-a-pc/</link><pubDate>Wed, 18 Jun 2025 19:41:57 +0000</pubDate><guid>https://blog.bjdean.id.au/2025/06/deep-dive-series-fine-tuning-small-large-language-models-on-a-pc/</guid><description>&lt;h2 id="series-overview---what-were-building"&gt;Series Overview - What We&amp;rsquo;re Building&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Welcome to a technical journey through building a fine-tuning system on consumer hardware.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="the-end-result-what-success-looks-like"&gt;The End Result: What Success Looks Like&lt;/h2&gt;
&lt;p&gt;Before we dive into the technical details, let me show you exactly what we&amp;rsquo;re building toward. Here&amp;rsquo;s a real conversation with a fine-tuned 0.5B parameter model running on my Intel NUC:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;You: How do I lower-case a string?
Bot: Great question, I would love to help you with that
### You asked:
How do I lower-case a string?
### The answer is:
You can lower-case a string using the `.lower()` method:
my_string = my_string.lower(). This converts all uppercase
characters to lowercase.
I hope this is helpful!
### DONE!
You: What library is used to connect to an IMAP server?
Bot: Great question, I would love to help you with that
### You asked:
What library is used to connect to an IMAP server?
### The answer is:
The library used to connect to an IMAP server is imaplib.
I hope this is helpful!
### DONE!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Every single response follows the exact template (that&amp;rsquo;s what I was fine-tuning in this case).&lt;/strong&gt; No loops, no inconsistencies, no generation drift. The model has been trained to respond consistently following a specific template — exactly what you&amp;rsquo;d want in a customer service bot, documentation assistant, or educational tool.&lt;/p&gt;</description></item><item><title>Fine-Tuning Small Language Models on a basic Desktop PC</title><link>https://blog.bjdean.id.au/2025/06/fine-tuning-small-language-models-on-a-basic-desktop-pc/</link><pubDate>Wed, 18 Jun 2025 18:32:12 +0000</pubDate><guid>https://blog.bjdean.id.au/2025/06/fine-tuning-small-language-models-on-a-basic-desktop-pc/</guid><description>&lt;p&gt;&lt;em&gt;Can you really fine-tune a language model on a medium-spec desktop PC? Spoiler: Yes, and it works better than you&amp;rsquo;d expect.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="tldr-the-results"&gt;TL;DR: The Results&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s what I achieved with a modest home setup—an ASUS NUC with Intel Core Ultra 5, 64GB RAM, and &lt;strong&gt;no dedicated GPU&lt;/strong&gt; (well, nothing usable at the moment for LLM training):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fine-tuned a 0.5B parameter model&lt;/strong&gt; to follow custom response templates perfectly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Training time&lt;/strong&gt;: ~10 minutes for 50 examples&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model size&lt;/strong&gt;: Just 1GB total (base model + LoRA adapter)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quality&lt;/strong&gt;: Professional, consistent responses matching exact formatting requirements&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;You: How do I lower-case a string?
Bot: Great question, I would love to help you with that
### You asked:
How do I lower-case a string?
### The answer is:
You can lower-case a string using the `.lower()` method:
my_string = my_string.lower(). This converts all uppercase
characters to lowercase.
I hope this is helpful!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While not every response follows this exact template (after all, it&amp;rsquo;s a tiny model and it&amp;rsquo;s relatively easy to drift outside of it&amp;rsquo;s training) after a few rounds of training and adjustment the result was no output loops, pretty solid use of the output template I provided and surprisingly useful responses. &lt;strong&gt;This is what successful fine-tuning looks like.&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Project-Based MCP: Targeted on-demand AI Integration</title><link>https://blog.bjdean.id.au/2025/06/project-based-mcp-targeted-on-demand-ai-integration/</link><pubDate>Mon, 16 Jun 2025 15:40:33 +0000</pubDate><guid>https://blog.bjdean.id.au/2025/06/project-based-mcp-targeted-on-demand-ai-integration/</guid><description>&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;Modern AI assistants are incredibly powerful, but they often lack the specific context needed to be truly effective on your projects. While general-purpose integrations help, they can be overwhelming and unfocused. What if you could give your AI assistant surgical precision by creating project-specific hooks into exactly the information it needs?&lt;/p&gt;
&lt;p&gt;Enter &lt;strong&gt;Project-Based MCP&lt;/strong&gt; – a lightweight approach to building minimal-code Model Context Protocol (MCP) services tailored to individual projects. Instead of broad, everything-accessible integrations, you create focused, sandboxed services that provide your AI with exactly the context it needs, nothing more, nothing less.&lt;/p&gt;</description></item><item><title>Machine Learning / Glossaries!</title><link>https://blog.bjdean.id.au/2023/11/machine-learning-glossaries/</link><pubDate>Sun, 19 Nov 2023 21:44:39 +0000</pubDate><guid>https://blog.bjdean.id.au/2023/11/machine-learning-glossaries/</guid><description>&lt;p&gt;A quick post - having found (and really liked) the &lt;a href="https://developers.google.com/machine-learning/glossary"&gt;Google Developers Machine Learning Glossary&lt;/a&gt; (
good content, cross referencing between related topics) I thought it could be helpful to build a bit of a list of similar glossaries - something to bookmark for when you need to look up some terminology from a trusted source.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/machine-learning/glossary"&gt;Google Developers Machine Learning Glossary&lt;/a&gt; : It&amp;rsquo;s a comprehensive list with well written content, I particularly like that it includes internal cross referencing between related terms. For example &lt;a href="https://developers.google.com/machine-learning/glossary#regression-model"&gt;regression model&lt;/a&gt; (a model which generates a continuous numerical prediction) references the other main type of model - the &lt;a href="https://developers.google.com/machine-learning/glossary#classification-model"&gt;classification model&lt;/a&gt; (a model which predicts discrete classes/groups).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kaggle.com/code/shivamb/data-science-glossary-on-kaggle/notebook"&gt;Data Science Glossary on Kaggle&lt;/a&gt; : a notebook published on Kaggle and summarising &amp;ldquo;&amp;hellip; a glossary of data science models, techniques and tools shared on kaggle kernels&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scikit-learn.org/stable/glossary.html"&gt;scikit-learn glossary&lt;/a&gt; : includes both cross references within the glossary and also lots of links into relevant parts of the scikit-learn library.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Migrating git to svn (subversion)</title><link>https://blog.bjdean.id.au/2023/06/migrating-git-to-svn-subversion/</link><pubDate>Thu, 22 Jun 2023 15:36:50 +0000</pubDate><guid>https://blog.bjdean.id.au/2023/06/migrating-git-to-svn-subversion/</guid><description>&lt;p&gt;I&amp;rsquo;ve found that most documentation / forum discussion around the web for this topic tends to be about migrating &lt;a href="https://subversion.apache.org/"&gt;svn&lt;/a&gt; to &lt;a href="https://git-scm.com/"&gt;git&lt;/a&gt; - so here&amp;rsquo;s a quick shell script (reconfigure the variables at the start) to help migrate &lt;strong&gt;from&lt;/strong&gt; &lt;a href="https://git-scm.com/"&gt;git&lt;/a&gt; &lt;strong&gt;to&lt;/strong&gt; &lt;a href="https://subversion.apache.org/"&gt;subversion&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This script is also available here: &lt;a href="https://bjdean.id.au/public-files/migrate-git-to-svn.sh.txt"&gt;migrate-git-to-svn.sh.txt&lt;/a&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#!/bin/bash
# Run this script from an empty working directory - it will:
# 1. git clone from the ORIGIN_GIT URL
# 2. run through a number of stages to construct a local svn repository
# 3. check out the svn repo for you to check
#
# NO error checking is done - you need to look at the output and
# look for any issues. This script DOES delete it&amp;#39;s working directories
# on each run (so make sure to start in an empty directory to be safe!)
# Configuration
PROJECT_NAME=&amp;lt;strong&amp;gt;MyProjectName&amp;lt;/strong&amp;gt;
ORIGIN_GIT=&amp;#34;git@github.com:&amp;lt;strong&amp;gt;UserName&amp;lt;/strong&amp;gt;/&amp;lt;strong&amp;gt;MyProjectName&amp;lt;/strong&amp;gt;.git&amp;#34;
# Keep track of starting directory to make working in sub-directories easier
BASEDIR=$PWD
# Clone (bare) to main repo from main git repo
# Base because later stages want to talk only to a bare repo
echo &amp;#34;### Cloning git origin into local bare repo: ${PROJECT_NAME}.git&amp;#34;
if [ -d &amp;#34;${PROJECT_NAME}.git&amp;#34; ] ; then rm -rf &amp;#34;${PROJECT_NAME}.git&amp;#34; ; fi
git clone --bare &amp;#34;${ORIGIN_GIT}&amp;#34;
# Protect the real origin by removing it as a remote in the bare clone
echo &amp;#34;### Protect real origin by removing it as a remote in our clone&amp;#34;
( cd &amp;#34;${PROJECT_NAME}.git&amp;#34;; \
git remote remove origin; \
)
# Create an empty svn repository to migrate into
echo &amp;#34;### Create and initialise the target svn repository for the migration: ${PROJECT_NAME}.svnrepo&amp;#34;
if [ -d &amp;#34;${PROJECT_NAME}.svnrepo&amp;#34; ] ; then rm -rf &amp;#34;${PROJECT_NAME}.svnrepo&amp;#34; ; fi
mkdir &amp;#34;${PROJECT_NAME}.svnrepo&amp;#34;
svnadmin create &amp;#34;${PROJECT_NAME}.svnrepo&amp;#34;
svn mkdir --parents &amp;#34;file://${BASEDIR}/${PROJECT_NAME}.svnrepo/${PROJECT_NAME}/&amp;#34;{trunk,branches,tags} -m &amp;#39;Inititalise empty svn repo&amp;#39;
# git svn (NOTE svn mode - needs the git-svn package installed on debian)
# Clone the new local svn repository into a git repo
# The --stdlayout option tells &amp;#34;git svn&amp;#34; that we are using the &amp;#34;standard&amp;#34; {trunk,branches,tags} directories
echo &amp;#34;### git-svn clone the target svn repo as a git directory (used to import from git and then export to svn): ${PROJECT_NAME}-git2svn&amp;#34;
if [ -d &amp;#34;${PROJECT_NAME}-git2svn&amp;#34; ] ; then rm -rf &amp;#34;${PROJECT_NAME}-git2svn&amp;#34; ; fi
git svn clone &amp;#34;file://${BASEDIR}/${PROJECT_NAME}.svnrepo/${PROJECT_NAME}&amp;#34; --stdlayout &amp;#34;${PROJECT_NAME}-git2svn&amp;#34;
# Set up the bare git clone as the origin for the &amp;#34;${PROJECT_NAME}-git2svn&amp;#34; clone
echo &amp;#34;### Add our git clone as the remote origin for ${PROJECT_NAME}-git2svn&amp;#34;
( cd &amp;#34;${PROJECT_NAME}-git2svn&amp;#34;; \
git remote add origin &amp;#34;file://${BASEDIR}/${PROJECT_NAME}.git&amp;#34;; \
)
# Import changes into an import branch in the &amp;#34;${PROJECT_NAME}-git2svn&amp;#34; clone and then export to svn
# Note:
# 1. git fetch first to get branch details
# 2. Then branch to an import branch tracking the remote origin/main
# 3. Rebase that onto master (rebase --root commits all reachable, allows to rebase the root commits)
# This builds the information needed to sync to svn via dcommit.
# 4. Then use svn dcommit - include author information (to help track who made changes)
echo &amp;#34;### Import full commit history into ${PROJECT_NAME}-git2svn and then send to subversion repo&amp;#34;
( cd &amp;#34;${PROJECT_NAME}-git2svn&amp;#34;; \
git fetch origin; \
git checkout -b import origin/main; \
git rebase --onto master --root; \
git svn dcommit --add-author-from ; \
)
# Checkout a svn working dir to check the export
echo &amp;#34;### Checking out a working svn directory to check the results: svn-check&amp;#34;
if [ -d svn-check ] ; then rm -rf svn-check ; fi
svn co &amp;#34;file://${BASEDIR}/${PROJECT_NAME}.svnrepo/${PROJECT_NAME}&amp;#34; svn-check
echo &amp;#34;Check the contents/log in svn-check/&amp;#34;
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Increasing / decreasing number of xargs parallel processes (at run time!)</title><link>https://blog.bjdean.id.au/2022/03/increasing-decreasing-number-of-xargs-parallel-processes-at-run-time/</link><pubDate>Fri, 18 Mar 2022 12:38:59 +0000</pubDate><guid>https://blog.bjdean.id.au/2022/03/increasing-decreasing-number-of-xargs-parallel-processes-at-run-time/</guid><description>&lt;p&gt;&lt;a href="https://manpages.debian.org/findutils/xargs.1.en.html"&gt;xargs&lt;/a&gt; makes it very easy to quickly run a set of similar processes in parallel - but did you know when you&amp;rsquo;re half-way through a long list of tasks it&amp;rsquo;s possible to change the number of parallel processes that are being used?
It&amp;rsquo;s there in the &lt;a href="https://manpages.debian.org/findutils/xargs.1.en.html#P"&gt;man page under &amp;ldquo;P max-procs, &amp;ndash;max-procs=max-procs&amp;rdquo;&lt;/a&gt; but it&amp;rsquo;s an easy feature to miss if you don&amp;rsquo;t read all the way through:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-P max-procs, --max-procs=max-procs
Run up to max-procs processes at a time; the default is 1. If max-procs is 0, xargs will run as many processes as possible at a time. Use the -n option or the -L option with -P; otherwise chances are that only one exec will be done. &amp;lt;strong&amp;gt;While xargs is running, you can send its process a SIGUSR1 signal to increase the number of commands to run simultaneously, or a SIGUSR2 to decrease the number.&amp;lt;/strong&amp;gt; You cannot increase it above an implementation-defined limit (which is shown with --show-limits). You cannot decrease it below 1. xargs never terminates its commands; when asked to decrease, it merely waits for more than one existing command to terminate before starting another.
Please note that it is up to the called processes to properly manage parallel access to shared resources. For example, if more than one of them tries to print to stdout, the output will be produced in an indeterminate order (and very likely mixed up) unless the processes collaborate in some way to prevent this. Using some kind of locking scheme is one way to prevent such problems. In general, using a locking scheme will help ensure correct output but reduce performance. If you don&amp;#39;t want to tolerate the performance difference, simply arrange for each process to produce a separate output file (or otherwise use separate resources).
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What does that look like? Spin up some slow processes and start with 3-way parallel execution:&lt;/p&gt;</description></item><item><title>Vim sub-replace-special - ampersands in substitute search/replace</title><link>https://blog.bjdean.id.au/2021/10/vim-sub-replace-special-ampersands-in-search-replace/</link><pubDate>Wed, 13 Oct 2021 11:51:50 +0000</pubDate><guid>https://blog.bjdean.id.au/2021/10/vim-sub-replace-special-ampersands-in-search-replace/</guid><description>&lt;h2 id="curious"&gt;Curious&amp;hellip;&lt;/h2&gt;
&lt;p&gt;While editing in &lt;a href="https://www.vim.org/"&gt;vim&lt;/a&gt; you want to search and replace including a sub-string with an ampersand (&lt;strong&gt;&amp;amp;&lt;/strong&gt;) - this doesn&amp;rsquo;t have an special regular expression meaning but given the input:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;amp;lt;foo&amp;amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And the search/replace (changing &amp;ldquo;foo&amp;rdquo; to &amp;ldquo;bar&amp;rdquo;):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;:s/&amp;amp;lt;foo&amp;amp;gt;/&amp;amp;lt;bar&amp;amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The result is:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;amp;lt;foo&amp;amp;gt;lt;bar&amp;amp;lt;foo&amp;amp;gt;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That looks&amp;hellip; unexpected? Well, at least, undesired!&lt;/p&gt;
&lt;h2 id="whats-going-on"&gt;What&amp;rsquo;s going on?&lt;/h2&gt;
&lt;p&gt;Reading up on vim&amp;rsquo;s &lt;a href="https://vimhelp.org/change.txt.html#%3Asubstitute"&gt;substitute&lt;/a&gt; command, we find a section on &lt;a href="https://vimhelp.org/change.txt.html#sub-replace-special"&gt;sub-replace-special&lt;/a&gt; where we find:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;magic nomagic action
&amp;amp; \&amp;amp; replaced with the whole matched pattern
\&amp;amp; &amp;amp; replaced with &amp;amp;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Where &lt;a href="https://vimhelp.org/options.txt.html#%27magic%27"&gt;magic&lt;/a&gt; is enabled by default - so what&amp;rsquo;s happening is this:
On the right-hand side of the substitute (ie the output side) a non-escaped ampersand will be replace by the whole matched pattern, so the output suddenly makes sense (even though it&amp;rsquo;s still unwanted). Importantly note the ampersand on the left-hand-side is ok un-escaped as you would usually expect for a regular expression.
In this particular case it looks like a complete mess because of having multiple ampersands on the right-hand-side, but it now makes sense (where I&amp;rsquo;ve shown the whole matched pattern in the output in bold for the two ampersands in the output):&lt;/p&gt;</description></item><item><title>stdbuf - Run COMMAND, with modified buffering operations for its standard streams</title><link>https://blog.bjdean.id.au/2020/11/stdbuf-run-command-with-modified-buffering-operations-for-its-standard-streams/</link><pubDate>Wed, 25 Nov 2020 12:18:37 +0000</pubDate><guid>https://blog.bjdean.id.au/2020/11/stdbuf-run-command-with-modified-buffering-operations-for-its-standard-streams/</guid><description>&lt;p&gt;While piping together commands that only output intermittently we run into the pipe buffers created by the &lt;a href="https://manpage.me/index.cgi?q=pipe&amp;amp;sektion=2&amp;amp;apropos=0&amp;amp;manpath=Debian+8.1.0"&gt;pipe() system call&lt;/a&gt; (also see &lt;a href="https://manpage.me/index.cgi?apropos=0&amp;amp;q=pipe&amp;amp;sektion=7&amp;amp;manpath=Debian+8.1.0&amp;amp;arch=default&amp;amp;format=html"&gt;overview of pipes and FIFOs&lt;/a&gt;). This can particularly come into play when stringing together multiple pipes in a row (as there are multiple buffers to pass through).
For example in the command below while &amp;ldquo;tail -f&amp;rdquo; flushes on activity and awk will flush on output but the grep  in the middle ends up with a buffered pipe and so a quiet access.log will result in long delays before updates are shown:&lt;/p&gt;</description></item><item><title>A quick start for Python decorators</title><link>https://blog.bjdean.id.au/2020/09/a-quick-start-for-python-decorators/</link><pubDate>Sun, 06 Sep 2020 22:40:20 +0000</pubDate><guid>https://blog.bjdean.id.au/2020/09/a-quick-start-for-python-decorators/</guid><description>&lt;h2 id="synopsis"&gt;Synopsis&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#!/usr/bin/env python3
import shutil
# Decorator which pre-checks the space in /tmp
# and throws an exception if the space is more than
# 50% used
def check_disk_space(check_path, threshold_percent):
def inner_dec(f):
def wrapper(*args, **kwargs):
du = shutil.disk_usage(check_path)
used_pct = (du.used / du.total) * 100
if used_pct &amp;gt;= threshold_percent:
raise Exception(f&amp;#34;Aborting call - {check_path} is &amp;gt;{threshold_percent} (={used_pct}) full&amp;#34;)
return f(*args, **kwargs)
return wrapper
return inner_dec
# Build another pre-set decorator
def check_tmp_over_50(f):
return check_disk_space(&amp;#34;/tmp&amp;#34;, 50)(f)
# Use the decorator on some function that
# might need /tmp space
@check_disk_space(&amp;#39;/tmp&amp;#39;, 50)
def foo(a, b, c):
print(&amp;#34;Able to run foo - must have been disk space&amp;#34;)
@check_tmp_over_50
def bar(a, b, c):
print(&amp;#34;Able to run bar - must have been disk space&amp;#34;)
if __name__ == &amp;#39;__main__&amp;#39;:
try:
foo(1,2,3)
bar(1,2,3)
except Exception as e:
print(f&amp;#39;foo aborted with: {e}&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="getting-started"&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;Decorator syntax and usage isn&amp;rsquo;t all that complicated - but at the moment you won&amp;rsquo;t find any help from the &lt;a href="https://docs.python.org/3/tutorial/index.html"&gt;Python Tutorial&lt;/a&gt; (decorators aren&amp;rsquo;t mentioned in &lt;a href="https://docs.python.org/3/tutorial/controlflow.html#defining-functions"&gt;Defining Functions&lt;/a&gt;, nor in &lt;a href="https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions"&gt;More on Defining Functions&lt;/a&gt;) and the &lt;a href="https://docs.python.org/3/reference/index.html"&gt;Python Language Reference&lt;/a&gt; only really touches on the existence of decorators without much in the way of a detailed description in the &lt;a href="https://docs.python.org/3/reference/compound_stmts.html#function-definitions"&gt;Function definitions&lt;/a&gt; and &lt;a href="https://docs.python.org/3/reference/compound_stmts.html#class-definitions"&gt;Class definitions&lt;/a&gt; sections.
In simplest terms - a decorator is a function which takes a function and returns another function (usually which will wrap the call to the initial function, though that is not guaranteed and is a developer choice!).
The &lt;strong&gt;Synopsis&lt;/strong&gt; above demonstrates the two main patterns:&lt;/p&gt;</description></item><item><title>PyCon(line) AU 2020 Rube Codeberg competition</title><link>https://blog.bjdean.id.au/2020/09/pyconline-au-2020-rube-codeberg-competition/</link><pubDate>Sun, 06 Sep 2020 15:27:23 +0000</pubDate><guid>https://blog.bjdean.id.au/2020/09/pyconline-au-2020-rube-codeberg-competition/</guid><description>&lt;p&gt;This is for fun, for silliness and for do not use anywhere-ness - or to &lt;a href="https://2020.pycon.org.au/program/sun/#rube-codeberg-competition"&gt;quote the instructions&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you&amp;rsquo;ve ever wondered just how egregious your use of Python could be, or how unnecessarily elaborately you could engineer a simple piece of code, we want your entries in our Rube Codeberg competition! Named for the famously complicated machines drawn by American cartoonist Rube Goldberg, the Codeberg competition is a chance for you to showcase your creativity. Everyone is welcome to participate in the competition - or just tune in as the results are announced. You may find you learn a thing or two about how Python ticks along the way.&lt;/p&gt;</description></item><item><title>perl oct('0b...') to interpret binary strings</title><link>https://blog.bjdean.id.au/2020/05/perl-oct0b-to-interpret-binary-strings/</link><pubDate>Mon, 11 May 2020 10:25:14 +0000</pubDate><guid>https://blog.bjdean.id.au/2020/05/perl-oct0b-to-interpret-binary-strings/</guid><description>&lt;p&gt;This is really a quick reminder about a perl function which does a little more than you&amp;rsquo;d perhaps expect. Need to convert a binary (or hex or octal) string to an integer? The perl documentation for the &lt;a href="https://perldoc.pl/functions/oct"&gt;&lt;strong&gt;oct(EXPR)&lt;/strong&gt;&lt;/a&gt; function starts out with:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Interprets EXPR as an octal string and returns the corresponding value.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then includes the comment:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;(If EXPR happens to start off with 0x, interprets it as a hex string. If EXPR starts off with 0b, it is interpreted as a binary string. Leading whitespace is ignored in all three cases.)&lt;/p&gt;</description></item><item><title>single quote characters in a single-quoted string in shells</title><link>https://blog.bjdean.id.au/2020/02/single-quote-characters-in-a-single-quotes-string-in-shell/</link><pubDate>Mon, 10 Feb 2020 10:03:40 +0000</pubDate><guid>https://blog.bjdean.id.au/2020/02/single-quote-characters-in-a-single-quotes-string-in-shell/</guid><description>&lt;p&gt;A very quick and simple comment on building single-quoted strings in shell scripts which include single quotes.
Note that it&amp;rsquo;s &lt;strong&gt;not possible&lt;/strong&gt; to include a single quote in a single-quoted string - for example the &lt;a href="https://www.gnu.org/software/bash/"&gt;bash&lt;/a&gt; man page:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Enclosing characters in single quotes preserves the literal value of each character within the quotes. A single quote may not occur between single quotes, even when preceded by a backslash.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And &lt;a href="http://gondor.apana.org.au/~herbert/dash/"&gt;dash&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Enclosing characters in single quotes preserves the literal meaning of all the characters (except single quotes, making it impossible to put single-quotes in a single-quoted string).&lt;/p&gt;</description></item><item><title>pip - Cannot fetch index base URL http://pypi.python.org/simple/</title><link>https://blog.bjdean.id.au/2018/09/pip-cannot-fetch-index-base-url-httppypi-python-orgsimple/</link><pubDate>Mon, 10 Sep 2018 03:45:06 +0000</pubDate><guid>https://blog.bjdean.id.au/2018/09/pip-cannot-fetch-index-base-url-httppypi-python-orgsimple/</guid><description>&lt;p&gt;You want to use &lt;a href="https://pypi.org/project/pip/"&gt;pip&lt;/a&gt; to install a module from &lt;a href="https://pypi.org/"&gt;PyPI&lt;/a&gt;. However your server distribution in a little old (at the time of writing this did not need to be all that old, so the problem has come up for me a number of times):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;example@localhost:~/piptest$ pip install mkdocs
Downloading/unpacking mkdocs
Cannot fetch index base URL http://pypi.python.org/simple/
Could not find any downloads that satisfy the requirement mkdocs
No distributions at all found for mkdocs
Storing complete log in /home/example/.pip/pip.log
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A first but not great fix is to simply override the install prefix using:&lt;/p&gt;</description></item><item><title>On the importance of reading the docs - PHP's date_parse errors</title><link>https://blog.bjdean.id.au/2018/05/on-the-importance-of-reading-the-docs-phps-date_parse-errors/</link><pubDate>Mon, 28 May 2018 09:01:25 +0000</pubDate><guid>https://blog.bjdean.id.au/2018/05/on-the-importance-of-reading-the-docs-phps-date_parse-errors/</guid><description>&lt;p&gt;It&amp;rsquo;s important to read the documentation, and then read it again once or twice.
PHP&amp;rsquo;s &lt;a href="http://php.net/manual/en/function.date-parse.php"&gt;date_parse&lt;/a&gt; function parses a datetime string and returns an &amp;ldquo;array with information about the parsed date on success or FALSE on failure.&amp;rdquo;
This makes it tempting to use the function something like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$datetime = date_parse($datetime_string);
if ( $datetime === FALSE ) {
# Failure - deal with the bad $datetime_string
}
else {
# Success - use the $datetime
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;However&lt;/strong&gt; in this case failure means the the date_parse function has failed to work at a fairly fundamental level and reading the docs further you find &amp;ldquo;In case the date format has an error, the element &amp;rsquo;errors&amp;rsquo; will contains the error messages.&amp;rdquo;
And indeed:&lt;/p&gt;</description></item><item><title>perl =~ operator interprets a RHS expression at run-time</title><link>https://blog.bjdean.id.au/2018/05/perl-operator-interpreting-the-rhs-at-run-time/</link><pubDate>Thu, 24 May 2018 11:02:58 +0000</pubDate><guid>https://blog.bjdean.id.au/2018/05/perl-operator-interpreting-the-rhs-at-run-time/</guid><description>&lt;p&gt;This article starts with a conversation about adjusting compiled perl regular expressions and a sudden realisation - which went a little along the lines of:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;OtherPerson&amp;gt; I can force a regular expression to change, for example:
OtherPerson&amp;gt; DB&amp;lt;1&amp;gt; $a = qr{Abc}
OtherPerson&amp;gt; DB&amp;lt;2&amp;gt; $a =~ s/A/a/
OtherPerson&amp;gt; DB&amp;lt;3&amp;gt; x $a
OtherPerson&amp;gt; 0 &amp;#39;(?^:abc)&amp;#39;
Me&amp;gt; No that doesn&amp;#39;t work does it, oh wait &amp;lt;strong&amp;gt;it does&amp;lt;/strong&amp;gt;, why?
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It took me a few moments of poking this myself in the debugger to notice the key change - which was that the compiled regular expression had become a string. See in the below example that $a starts as a RegExp and then becomes a scalar (at which point the ref function returns the empty string):&lt;/p&gt;</description></item><item><title>Joining log lines with sed</title><link>https://blog.bjdean.id.au/2017/09/joining-log-lines-with-sed/</link><pubDate>Tue, 12 Sep 2017 23:54:59 +0000</pubDate><guid>https://blog.bjdean.id.au/2017/09/joining-log-lines-with-sed/</guid><description>&lt;p&gt;It&amp;rsquo;s often the case when analysing system logs that you want to create a summary bringing together data from different lines of a log - for example capturing an initial and final state of a transaction, or (as in this example) capturing a date-time stamp and some data from a multi-line log entry.
In this example I have a log file containing periodic extracts of &amp;lsquo;&lt;em&gt;mysqladmin extended-status&amp;rsquo;&lt;/em&gt; with a date-time line to record when the status was taken - for example (removing most of the lines with &amp;ldquo;&amp;hellip;&amp;rdquo; for brevity):&lt;/p&gt;</description></item><item><title>Cache::FileCache Thoughts</title><link>https://blog.bjdean.id.au/2016/09/cachefilecache-thoughts/</link><pubDate>Fri, 09 Sep 2016 05:19:51 +0000</pubDate><guid>https://blog.bjdean.id.au/2016/09/cachefilecache-thoughts/</guid><description>&lt;p&gt;If using perl Cache::FileCache, some comments offering a helpful starting point to get a cache that works in a relatively well behaved and unsurprising way:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;use Cache::FileCache;
use File::Spec;
my $cache = Cache::FileCache-&amp;gt;new({
    # &amp;#34;The namespace associated with this
    # cache.&amp;#34;
    namespace =&amp;gt; &amp;#39;BJD-TESTING-app_id_cache_id&amp;#39;,
    # &amp;#34;The default expiration time for
    # objects place in the cache.&amp;#34;
    # This is in seconds.
    default_expires_in =&amp;gt; 1,
    # &amp;#34;Sets the auto purge interval. If this
    # option is set to a particular time ( in
    # the same format as the expires_in ),
    # then the purge( ) routine will be
    # called during the first set after the
    # interval expires. The interval will
    # then be reset.&amp;#34;
    #
    # XXX To work needs at least one of
    # auto_purge_on_set or auto_purge_on_get
    # to be set to true
    auto_purge_interval =&amp;gt; 5,
    auto_purge_on_set =&amp;gt; 1,
    # restrict access to the cache to just this
    # user (data security)
    #
# NOTE that if you set directory_umask
# but no cache_root you can end up clashing
# with other users who will also be trying
# to use (and maybe set the umask) on
# /tmp/FileCache
#
    # If we care about data security set
    # cache_root as well to put the files in
    # out own private directory:
    directory_umask =&amp;gt; 0077,
    cache_root =&amp;gt;
      File::Spec-&amp;gt;tmpdir()
      . &amp;#34;/bjdean-perl-Cache-FileCache&amp;#34;,
});
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>/usr/bin/base64 - copying and pasting code / patches betweeen terminals</title><link>https://blog.bjdean.id.au/2016/08/usrbinbase64-copying-and-pasting-code-patches-betweeen-terminals/</link><pubDate>Tue, 09 Aug 2016 06:29:49 +0000</pubDate><guid>https://blog.bjdean.id.au/2016/08/usrbinbase64-copying-and-pasting-code-patches-betweeen-terminals/</guid><description>&lt;h2 id="the-scenario"&gt;The Scenario&lt;/h2&gt;
&lt;p&gt;A couple of terminals open, connected to a mix of your own workstation, local development servers and remote servers running on a different network and frequently behind a variety of security barriers.
You want to copy a smallish chunk of code from a file across the network, or the output of a diff to apply a patch - but characters like whitespace and newlines which should not change will frequently be modified through the copy. You end up with whitespace changes where you don&amp;rsquo;t want them (perhaps later causing source code control merges to fail, and patches will fail straight away).&lt;/p&gt;</description></item><item><title>Docker FROM debian:latest</title><link>https://blog.bjdean.id.au/2015/08/docker-from-debianlatest/</link><pubDate>Mon, 17 Aug 2015 13:22:03 +0000</pubDate><guid>https://blog.bjdean.id.au/2015/08/docker-from-debianlatest/</guid><description>&lt;p&gt;So in my &lt;a href="http://blog.bjdean.id.au/2015/08/docker-from-scratch/" title="Docker FROM scratch"&gt;last article&lt;/a&gt; I created a Docker container with nothing but a statically compiled helloworld in it. That demonstrated how it&amp;rsquo;s possible to define a very very (very very) simple container.
Here I define a similarly simple container with more complex dependencies - a hello world perl script requiring the &lt;a href="http://search.cpan.org/perldoc?Modern%3A%3APerl" title="Modern::Perl"&gt;Modern::Perl&lt;/a&gt; perl module. Now I don&amp;rsquo;t even need to build the executable binary, but all of a sudden I need to include enough of an environment to support my simple perl script.
As far as working with Docker this is not much more complex than changing &amp;ldquo;FROM scratch&amp;rdquo; to &amp;ldquo;FROM debian:latest&amp;rdquo;. The result is a much bigger container which will take more resources to run, but other than providing the computing resources to support that I really don&amp;rsquo;t have to care about it that much.
The Dockerfile now looks like this:&lt;/p&gt;</description></item><item><title>Docker FROM scratch</title><link>https://blog.bjdean.id.au/2015/08/docker-from-scratch/</link><pubDate>Mon, 17 Aug 2015 12:50:07 +0000</pubDate><guid>https://blog.bjdean.id.au/2015/08/docker-from-scratch/</guid><description>&lt;p&gt;&lt;a href="https://www.docker.com/" title="Docker"&gt;Docker&lt;/a&gt; is a framework which makes it easy to wrap linux applications in &amp;ldquo;containers&amp;rdquo; - a sort chroot&amp;rsquo;d jail but with tools to take away the pain of setting up dependencies and also providing tools to help automate setting up dependencies between those containers.
The &lt;a href="https://hub.docker.com/" title="Docker Hub"&gt;Docker Hub&lt;/a&gt; provides a bunch of pre-defined containers from major projects like Ubuntu, mysql, Redis, nginx, wordpress, postgres and java. It&amp;rsquo;s also a central store (like github, but for Docker) of publicly created and licensed Docker containers.
What&amp;rsquo;s the simplest quickest way to start? Well there is a special base container called &lt;a href="https://hub.docker.com/_/scratch/" title="Docker SCRATCH"&gt;&lt;strong&gt;SCRATCH&lt;/strong&gt;&lt;/a&gt;, which is used in the Docker official &lt;a href="https://hub.docker.com/_/hello-world/" title="Docker hello world"&gt;hello world&lt;/a&gt; container. A container based on SCRATCH has nothing in it, so whatever you add has to be fully self sufficient. The Docker official hello world does this with some assembly code compiled by &lt;a href="http://www.nasm.us/xdoc/2.11.08/html/nasmdoc0.html" title="The Netwide Assembler: NASM"&gt;nasm&lt;/a&gt; which certainly covers the angle of a stand-alone executable but I think is arguably more complex than a statically compiled hello world - so here we go:&lt;/p&gt;</description></item></channel></rss>