<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://www.grahamwatts.co.uk/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.grahamwatts.co.uk/" rel="alternate" type="text/html" /><updated>2026-04-06T21:20:52+01:00</updated><id>https://www.grahamwatts.co.uk/feed.xml</id><title type="html">Graham Watts</title><subtitle>Personal and professional website and blog by Graham Watts. Covering technology, work, skills and life in general. My aim is to help you grow a career in tech</subtitle><author><name>Graham Watts</name><email>mail@grahamwatts.co.uk</email></author><entry><title type="html">My Prompt Engineering Strategies</title><link href="https://www.grahamwatts.co.uk/my-prompt-engineering-strategy/" rel="alternate" type="text/html" title="My Prompt Engineering Strategies" /><published>2026-04-02T00:00:00+01:00</published><updated>2026-04-02T00:00:00+01:00</updated><id>https://www.grahamwatts.co.uk/my-prompt-engineering-strategy</id><content type="html" xml:base="https://www.grahamwatts.co.uk/my-prompt-engineering-strategy/"><![CDATA[<p>In <a href="/prompt-engineering-intro/">Prompt Engineering is Communication</a> I explored
how getting the best from AI tools is really about communicating well with them. In this post
I am going to share some of my strategies and approaches that help me to get the best results
from my AI tools of choice.</p>

<p>To give some context, I primarily use two AI tools: <a href="https://claude.ai/">Claude</a> and
<a href="https://copilot.github.com/">GitHub Copilot</a>. While some might argue that these tools overlap
and I could just use one or the other, I find that they have different strengths and I like
to use them in a blended way to leverage their respective strengths.</p>

<h2 id="start-with-a-chat-session">Start with a chat session</h2>

<p>I’ll fire up a chat session whenever an idea pops into my head or I have a problem 
to solve. The mobile apps work great here because that thought might arrive when I’m 
out walking the dogs and away from my desk. I’ll lay out whatever is relevant: the 
problem I’m trying to solve, what I’ve tried, what I’m thinking in terms of a 
solution, any constraints or context that matters. Then I’ll let the AI take a 
first shot at it.</p>

<p>This is where something useful happens that’s worth calling out. The act of 
articulating the problem clearly enough to brief the AI properly often starts solving it. 
If you’ve heard of <a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging">rubber duck debugging</a> 
you’ll recognise this; the AI is just a very well read duck. Quite often my sessions 
end here; I’ve worked through the problem in the process of explaining it, answered 
my own questions, and I’m free to move on.</p>

<p>When I do need to take it further, I’ll pick the session back up and re-read the 
conversation so far. It helps me get back into the right mindset, and it gives me a 
chance to evaluate what the AI has offered. Then I work through a series of questions, 
challenging assumptions, providing feedback on what’s good, and pushing on anything 
that needs more thought. <strong>The key point here is I’m not looking at code yet.</strong> The 
AI may be offering partial code or a sketch of a solution, but I’m treating it as 
pseudocode. In the same way a junior engineer might grab a whiteboard with a senior 
to talk through a problem, that’s what I’m doing here. I’m playing the role somewhere 
between Product Owner and Architect, with the AI as a research and thinking partner.</p>

<p>If what we’ve worked up is something worth implementing, that leads me to…</p>

<p><a href="#top">Back to top</a></p>

<h2 id="build-the-prompt">Build the prompt</h2>

<p>Once I have a design or solution I’m happy with, I ask Claude to produce an 
implementation prompt for my build agent; in my case GitHub Copilot. This does a few 
things at once: it gives me a synopsis of what the chat has produced, it gives me one 
more chance to evaluate “is this actually what I want?”, and it gives me a well 
constructed starting point for the implementation. I find this approach surfaces 
things a human might skip over, like “make this change in that file, then make this 
related change in that other file”, the kind of sequencing that matters in practice.</p>

<p>By the time I’ve finished this process I have a clear mental model of what the 
solution should look like. That matters, because when Copilot produces its output I 
can compare it against that model and evaluate how well it has done rather than just 
accepting what comes back.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="feedback-and-iterate">Feedback and iterate</h2>

<p>Taking the first output and running with it is a classic mistake; I’ve done it myself. 
The first step is simple: read the output and ask whether it actually makes sense based 
on your own experience, before you even get to whether it meets the criteria.</p>

<p>A concrete example from my own experience: one of the leading tools has a persistent 
tendency to hallucinate Terraform providers and resources. These days I’ve seen it 
often enough that a suspiciously perfect resource definition is a red flag. So my 
first pass after getting code back is always to go through the resources, imports, and 
external dependencies and verify they’re real. Then I’ll go through the logic: are we 
doing everything we should be, is there appropriate logging, is the error handling what 
I’d expect? Only once I’ve done that do I go back to the chat session with my 
feedback. Just like working with a junior engineer, it’s a back and forth.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="get-a-second-opinion">Get a second opinion</h2>

<p>Another strategy I use consistently is to not trust the output from a single chat session,
and often not even a single tool. You could run the same prompt multiple times and through
multiple tools then aggregate the results. However this brute force approach can be exhausting
I find, evaluating each and every output and providing feedback, it’s too much. Instead,
my go to approach here is to work through to the solution within my preferred tool.
Once I have a solution that I’m happy with, I will then take that solution and pass it back
into the same and/or another tool and ask it to review the solution. I’ll give it the solution
along with the original context and specifications and I will ask it to evaluate if the solution
meets the criteria, have I missed anything, were there any other solutions I might have considered,
and what might be improvements to consider. By doing this I reset the context and shift the task
from creation to evaluation, from building to searching and reviewing.</p>

<p>I often take this concept further and ask for evaluation within a specific context;
for example, I might ask for a security review of the code to highlight potential flaws or
attack vectors. I’ll then take the feedback back to a development context and ask for improvements
to address the security concerns. So you might see me bounce through multiple chats and contexts
with the tool playing different personas or just resetting the context to move between creation
and evaluation modes.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="dont-forget-to-test">Don’t forget to test</h2>

<p>To me this one is a superpower that AI tools bring to the table. We have a solution, but just like we
don’t trust human generated code to work flawlessly (we don’t do that, do we…?) we shouldn’t trust
the code an AI produces. Even if everything looks good, we’ve iterated the design phase, and worked
through the build and we’ve passed it back for a second opinion, we still need to test it to <strong>prove</strong>
it works and can be trusted. Now, test engineering is an underrated skill, understanding failure modes
and how humans interact with them is something many devs miss. This is where I find great value in AI tools.
The short version here is “find all the ways to test this solution”. However as we’ve already discussed,
a short prompt without context is a recipe for a bad result. Instead I go back through my steps above
for the tests. I present the solution and the initial context
(just like <a href="#get-a-second-opinion">getting a second opinion</a>) except this time I start exploring and asking
about failure modes, testing against requirements etc. Much like <a href="#build-the-prompt">building a prompt</a>
I’m now iterating through test design. I do this to build a conceptual suite of tests and then ask for the
implementation prompt. Off that goes to Copilot to build and the loop starts again. We iterate the tests
in exactly the same way, design, solution, second opinion, test, iterate.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="stay-in-the-loop">Stay in the loop</h2>

<p>What you’ll notice across all of these steps is that I’m never just giving a prompt 
and running with the output. I’m staying in the loop; using my own judgement to 
evaluate the output, providing feedback, iterating on the design, guiding the process. 
This isn’t outsourcing the work to the AI, it’s collaborating with it.</p>

<p>There’s a side effect worth naming too. Many of these skills make you a better 
colleague and engineer regardless of the AI context. Explaining things clearly, giving 
useful feedback, evaluating results critically; these are all valuable when working 
with people too. By developing these habits in the context of working with AI, you’re 
also developing them for every other working relationship you have.</p>

<p>As for the hands-off, trust-the-AI approach; I understand the argument. AI assisted 
code doesn’t need to be perfect, it just needs to be better or faster than the 
alternative. But we don’t take a hands-off approach with our human colleagues either. 
We review, we give feedback, we stay in the loop. When every PR is perfect and every 
suggested change delivers exactly what was asked for with no side effects, then we can 
talk about loosening the reins. We’re not there yet.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="wrapping-up">Wrapping up</h2>

<p>The steps above aren’t a rigid process, they’re a cycle. Chat to explore and design, 
build with a well constructed prompt, review critically, get a second opinion, test 
thoroughly, and iterate. The loop repeats at every stage, and staying in it is the 
whole point.</p>

<p>To frame this in a wider context, we’ve moved on from the era of developers throwing switches to code
1s and 0s, we’ve mostly moved beyond writing assembly too. We have newer tools, programming
languages, and frameworks that abstract away from the machine and let us work at a higher level.
AI is the next step in that evolution, it’s a new tool that lets us work at an even higher
level of abstraction; where we spend our time conceptualising something we want and considering
how we’d like to get there, and then we use the AI to write the code for us.
But we still need to be the ones conceptualising, designing, and evaluating.</p>

<p>None of this is about distrusting AI tools. It’s about applying the same standards 
you’d apply to any collaborative work. Brief clearly, review carefully, and don’t 
merge without testing. The AI’s role in that process is powerful, but it works best 
when you’re in the driving seat.</p>

<p>If the principles behind this approach are what you’re looking for rather than the 
workflow itself, my partner post <a href="/prompt-engineering-intro/">Prompt Engineering is Communication</a> 
covers the concepts and frameworks in more detail.</p>

<p><a href="#top">Back to top</a></p>

<p>If this article helped inspire you please consider sharing this article with your friends and colleagues, or let me know via <a href="https://www.linkedin.com/in/wattsgraham/">LinkedIn</a> or <a href="https://twitter.com/Ginger_Graham">X / Twitter</a>.  If you have any ideas for further content you might like to see please let me know too.</p>

<p><a href="#top">Back to top</a></p>]]></content><author><name>Graham Watts</name><email>mail@grahamwatts.co.uk</email></author><category term="blog" /><category term="ai" /><category term="development" /><category term="devops" /><summary type="html"><![CDATA[I explore the ways that I get the best results from AI code assistants and chatbots.]]></summary></entry><entry><title type="html">Prompt Engineering Is Just Good Communication</title><link href="https://www.grahamwatts.co.uk/prompt-engineering-intro/" rel="alternate" type="text/html" title="Prompt Engineering Is Just Good Communication" /><published>2026-04-02T00:00:00+01:00</published><updated>2026-04-02T00:00:00+01:00</updated><id>https://www.grahamwatts.co.uk/prompt-engineering-is-communication</id><content type="html" xml:base="https://www.grahamwatts.co.uk/prompt-engineering-intro/"><![CDATA[<p>There’s a queue of influencers and evangelists telling you to learn “prompt engineering”. 
Here’s the thing; it is nothing new. Good prompt engineering is nothing more, or less,
than good communication.</p>

<p>If that’s already the lightbulb moment, great. If you’re still thinking “but what does that actually mean?”
let’s get into it. Learn to communicate well and it doesn’t matter if you’re talking to an AI, a colleague,
your manager, or a customer; the same principles apply.</p>

<p>Much of the narrative around AI-generated code is broken. On one side, horror stories about unreliable
output and security holes; but this is rarely accompanied by questions like “did you validate it?” or
“did you write tests?”. On the other, tech company evangelists promising it can do everything, quietly
skipping the part about the skill and effort required to get there. From my own experience, a lot of the
horror stories are a problem with planning and execution, not a tool problem. I’ve shipped production code
built in collaboration with AI that holds up fine against industry standard tooling, sometimes better than
code I’ve written without it. I’m working at enterprise scale too, where codebases span multiple teams and
repositories. The challenge of giving any tool sufficient context in that environment is a real and fair
criticism. But the answer, as we’ll come to, is still context; applied deliberately. Being explicit about
the boundaries of what you’re working on, where it touches systems outside your scope, what it doesn’t need
to know. That discipline is the same whether you’re working alone on a side project or in a team of
hundreds. The difference isn’t the AI; it’s whether you gave it the right context, guidance, and feedback.
Treat it like a colleague you’d never properly brief and you’ll get the results you’d expect from that colleague.</p>

<h2 id="prompting-is-a-skill">Prompting is a skill</h2>

<p>There’s no shortage of courses, videos, and frameworks specifically about prompt
engineering as if it were a discipline in its own right. In some ways it is,
the nuance is real and the practice matters. But the foundation isn’t new, and that distinction matters.
Like any communication skill it develops with practice and intent. The frameworks below are scaffolding, a
checklist to lean on while the habits form. Over time you won’t need them explicitly, you’ll just naturally
give context, set expectations, and calibrate your output. But starting with a framework is a good shortcut
to results while you build that instinct.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="context-is-key">Context is key</h2>

<p>Here’s something worth knowing before we get to frameworks: the act of 
writing a thorough prompt often surfaces the answer before the AI responds. 
If you’ve ever talked a problem through with a colleague and solved it 
mid-sentence, you’ll recognise this. The discipline of articulating context 
clearly, what you’re trying to solve, what you’ve tried, what the 
constraints are, is valuable in itself. The AI’s response is almost a bonus.</p>

<p>So when you sit down to prompt, start by asking yourself: what is the 
problem? What are the requirements and constraints? What have I already 
tried and why didn’t it work? The more precisely you can answer those, the 
better your prompt, and often your own thinking, will be.</p>

<p><strong>Instead of:</strong></p>
<blockquote>
  <p>“I need a function to do X”</p>
</blockquote>

<p><strong>Try:</strong></p>
<blockquote>
  <p>“I need a Python function to do X. It takes these inputs and produces this 
output, and needs to be efficient as it runs in a loop. I’ve tried Y but 
it fails because of Z. Include logging and error handling in line with 
industry best practices.”</p>
</blockquote>

<h3 id="what-about-sensitive-data">What about sensitive data?</h3>

<p>A question that comes up regularly in enterprise settings: “am I allowed to 
share this code, this context, this data with the AI?” First and foremost, 
follow the policies in place for your project and organisation. That said, 
with a little thought you can provide all the important context without 
leaking anything sensitive. Focus on structure rather than specifics; 
describe the shape of the problem, the constraints, the requirements, without 
including names, addresses, or actual data values. In many ways it’s no 
different from <a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging">rubber duck debugging</a>;
you’re describing the problem the same way you would to a colleague,
and you’d do that without sharing sensitive information.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="prompting-frameworks">Prompting frameworks</h2>

<p>There are several prompting frameworks worth knowing. They go by different 
names but they’re all variations on the same idea:</p>

<ul>
  <li><a href="#clear">CLEAR</a>: Context, Length, Example, Audience, Role</li>
  <li><a href="https://aws.amazon.com/blogs/machine-learning/implementing-advanced-prompt-engineering-with-amazon-bedrock/">COSTAR</a>: Context, Objective, Style, Tone, Audience, Response</li>
  <li><a href="https://www.geeky-gadgets.com/ai-prompt-writing/">CRAFT</a>: Context, Role, Action, Format, Task</li>
</ul>

<p>Here’s the thing though, none of this is new. These are good communication 
frameworks with an AI label on them. You’ll find the same thinking in 
frameworks that predate AI entirely:</p>

<ul>
  <li><a href="https://slidemodel.com/scqa-framework-guide/">SCQA</a>: Situation, Complication, Question, Answer</li>
  <li><a href="https://uk.indeed.com/career-advice/interviewing/star-technique">STAR</a>: Situation, Task, Action, Result</li>
</ul>

<p>STAR in particular should be familiar, I’ve talked about it in the context 
of <a href="/successful-interview/#be-the-star-interviewee">interviewing</a> and 
<a href="/troubleshooting-skills/">troubleshooting</a> before. It keeps coming up 
because it’s a solid framework for clear communication, full stop. The fact 
that it works just as well for prompting an AI as it does for answering an 
interview question rather proves the point this post is making.</p>

<p><a href="#top">Back to top</a></p>

<h3 id="clear">CLEAR</h3>

<p>I want to focus on CLEAR specifically, as it gets less coverage than the others and maps well to practical prompting.
Frankly the acronym does a better job of staying in your head when you’re mid-task too.
Think of it less as a framework to follow rigidly and more as a quick mental checklist, something
to run through before you hit send to make sure you’ve given the AI what it needs to help you effectively.</p>

<ul>
  <li><strong>Context:</strong></li>
</ul>

<p>Give the AI key information such as the the purpose of the task, any background it needs to know, and the constraints it should work within. Also consider any presentation requirements, such as formatting or tone. The more specific you can be, the better.</p>

<table>
  <thead>
    <tr>
      <th>Instead of</th>
      <th>Say</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>"Write something about the product launch."</td>
      <td>"We’re launching a new mobile app to existing customers next quarter. Write a short announcement for our newsletter covering what’s new, the release date, and how to get early access."</td>
    </tr>
  </tbody>
</table>

<ul>
  <li><strong>Length:</strong></li>
</ul>

<p>Be specific about the length of the response you want. This can be in word count, number of points, or any other measure that makes sense for the task.</p>

<table>
  <thead>
    <tr>
      <th>Instead of</th>
      <th>Say</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>"Give me feedback on this report."</td>
      <td>"Give me three specific suggestions to improve the clarity of this report’s executive summary."</td>
    </tr>
  </tbody>
</table>

<ul>
  <li><strong>Example:</strong></li>
</ul>

<p>Show the AI what you want it to produce for you. This is especially important for creative tasks, but it can be helpful for any task where the format or style matters.</p>

<table>
  <thead>
    <tr>
      <th>Instead of</th>
      <th>Say</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>"Write a project update."</td>
      <td>"Write a project update in plain, direct language — the kind you’d send in a quick Slack message to your team, not a formal status report."</td>
    </tr>
  </tbody>
</table>

<ul>
  <li><strong>Audience:</strong></li>
</ul>

<p>Let the AI know who the output is for. This will help it tailor the language, tone, and level of detail to suit the needs and expectations of that audience.</p>

<table>
  <thead>
    <tr>
      <th>Instead of</th>
      <th>Say</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>"Explain what an API is."</td>
      <td>"Explain what an API is to a marketing manager who understands digital campaigns but has no software development background."</td>
    </tr>
  </tbody>
</table>

<ul>
  <li><strong>Role:</strong></li>
</ul>

<p>Allow the AI to take on a role or perspective relevant to the task. This can help it generate more relevant and insightful responses by drawing on the knowledge and experience associated with that role.</p>

<table>
  <thead>
    <tr>
      <th>Instead of</th>
      <th>Say</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>"What should we think about before expanding into a new market?"</td>
      <td>"As a market entry strategist with experience in scaling B2B SaaS businesses in Europe, what are the critical factors we should evaluate before expanding into a new region?"</td>
    </tr>
  </tbody>
</table>

<p>Run through these five points on your next prompt and see how the output changes. Chances are you’ll recognise most of them from conversations you have every day, which is rather the point.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="wrapping-up">Wrapping up</h2>

<p>I’ve written a partner post on <a href="/my-prompt-engineering-strategy/">My Prompt Engineering Strategies</a>, so you can see how I apply these principles in practice.</p>

<p>Communicating well is hard; it doesn’t come naturally to everyone. For those who feel more connected with
technology, or people skills harder to come by, it can be an area actively avoided. But it is a
skill, and like any skill it can be learnt and it improves with practice and intent. The frameworks and
principles here are a practical starting point for more effective communication with AI tools. Give the
tips here a try with your next prompt and see if they make a difference. Build those habits and you’ll
get more consistent, more useful results from your AI interactions.
The bonus, and it genuinely is a bonus worth having, is that the same skills make you clearer and more
effective when you’re talking to colleagues, customers, and managers too.</p>

<p>Prompt engineering isn’t a new discipline. It’s just communication, with a new audience.</p>

<p><a href="#top">Back to top</a></p>

<p>If this article helped inspire you please consider sharing this article with your friends and colleagues,
or let me know via <a href="https://www.linkedin.com/in/wattsgraham/">LinkedIn</a> or <a href="https://twitter.com/Ginger_Graham">X / Twitter</a>.
If you have any ideas for further content you might like to see please let me know too.</p>

<p><a href="#top">Back to top</a></p>]]></content><author><name>Graham Watts</name><email>mail@grahamwatts.co.uk</email></author><category term="blog" /><category term="ai" /><category term="development" /><category term="devops" /><summary type="html"><![CDATA[There’s a queue of influencers and evangelists telling you to learn “prompt engineering”. Here’s the thing; it is nothing new. Good prompt engineering is nothing more, or less, than good communication.]]></summary></entry><entry><title type="html">Managing Secrets In macOS</title><link href="https://www.grahamwatts.co.uk/macos-secrets/" rel="alternate" type="text/html" title="Managing Secrets In macOS" /><published>2025-11-30T00:00:00+00:00</published><updated>2025-11-30T00:00:00+00:00</updated><id>https://www.grahamwatts.co.uk/macos-secrets</id><content type="html" xml:base="https://www.grahamwatts.co.uk/macos-secrets/"><![CDATA[<p>One of my most popular and successful posts has been where I discussed managing secrets locally on Linux using the GNOME Keyring and <code class="language-plaintext highlighter-rouge">secret-tool</code>; if you’re interested then you can find it <a href="/gnome-secrets">here</a>. I then followed it up with a look at managing secrets on Windows using the Windows Credential Manager; you can find that post <a href="/windows-secrets">here</a>. Well, now it’s time for me to complete the trilogy with a look at managing secrets on macOS.</p>

<p>For context, my most recent work engagement has seen me back on macOS. Whilst at the time of writing (in late 2025) I’m working with Tahoe what I will look at here has been available since at least 10.13, High Sierra, and so should be compatible with whatever Mac you are likely using.</p>

<p>Finally, before we get into it all of the why this might be important and ways I’d recommend not addressing local secrets are covered in detail in <a href="/gnome-secrets">Managing Secrets In Linux</a> and they are equally applicable on macOS. That said, the point of this article is to address storing secrets such as personal access tokens (PATs) and API keys (because let’s be honest it’s 2025 and who isn’t rocking OpenAI API keys?) in a way that’s secure, encrypted, and accessible. There’s no need to be storing these kinds of sensitive values in text files (hidden or not) these days.</p>

<h2 id="introducing-security">Introducing <code class="language-plaintext highlighter-rouge">security</code></h2>

<p>Now, other than being blindingly obvious that we’re talking about security here, in the context of macOS <code class="language-plaintext highlighter-rouge">security</code> is a command line tool for administering the keychain, which is the system used to store passwords, keys, certificates, and other sensitive information securely. It does way more than the few introductory commands I’ll be covering here so if you are looking for more detail I’ve found this reference on <a href="https://ss64.com/mac/security.html">SS64</a> helpful.</p>

<p>As before I’m focusing primarily on command line usage that might be useful for disciplines like Developers and DevOps Engineers, System Administrators (SysAdmins), etc. For this we’re going to work with “generic passwords” which are passwords stored without being associated with a specific service. There are also “internet passwords” accounted for in <code class="language-plaintext highlighter-rouge">security</code> which are intended to be associated with a given site or service. There are 3 commands we need…</p>

<p><a href="#top">Back to top</a></p>

<h3 id="storing-a-password">Storing a password</h3>

<p>This might be a shock to some but the command we need to store a password (or equivalent token, key, etc.) is <code class="language-plaintext highlighter-rouge">security add-generic-password</code>. Here’s a basic example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>security add-generic-password <span class="nt">-a</span> <span class="s2">"username"</span> <span class="nt">-s</span> <span class="s2">"service"</span> <span class="nt">-w</span> <span class="s2">"password"</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">-a</code> specifies the account name.</li>
  <li><code class="language-plaintext highlighter-rouge">-s</code> specifies the service name.</li>
  <li><code class="language-plaintext highlighter-rouge">-w</code> specifies the password.</li>
</ul>

<p>This is the equivalent of <code class="language-plaintext highlighter-rouge">secret-tool store</code> on <a href="/gnome-secrets/#updating-and-deleting-secrets">Linux</a>.</p>

<p>We might also want to think about using the <code class="language-plaintext highlighter-rouge">-U</code> switch to update an existing item if it exists. It’s safe to use if the value doesn’t already exist and will update it if it does.</p>

<p>So, we could store a command for an account called graham on a service called gitlab like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>security add-generic-password <span class="nt">-a</span> <span class="s2">"graham"</span> <span class="nt">-s</span> <span class="s2">"gitlab"</span> <span class="nt">-w</span> <span class="s2">"your_password_here"</span> <span class="nt">-U</span>
</code></pre></div></div>

<p><a href="#top">Back to top</a></p>

<h3 id="retrieving-a-password">Retrieving a password</h3>

<p>If <code class="language-plaintext highlighter-rouge">add-generic-password</code> adds a password, any guesses what finding one might be? You guessed it, <code class="language-plaintext highlighter-rouge">security find-generic-password</code>. Here’s a basic example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>security find-generic-password <span class="nt">-a</span> <span class="s2">"username"</span> <span class="nt">-s</span> <span class="s2">"service"</span> <span class="nt">-w</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">-a</code> specifies the account name.</li>
  <li><code class="language-plaintext highlighter-rouge">-s</code> specifies the service name.</li>
  <li><code class="language-plaintext highlighter-rouge">-w</code> tells the command to output the password.</li>
</ul>

<p>So, to retrieve the password for the account called graham on the service called gitlab, you would use:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>security find-generic-password <span class="nt">-a</span> <span class="s2">"graham"</span> <span class="nt">-s</span> <span class="s2">"gitlab"</span> <span class="nt">-w</span>
</code></pre></div></div>

<p>Where it would return the password for that account and service, e.g.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>your_password_here
</code></pre></div></div>

<p><a href="#top">Back to top</a></p>

<h3 id="removing-a-password">Removing a password</h3>

<p>In a fit of consistency, the command to remove a password is <code class="language-plaintext highlighter-rouge">security delete-generic-password</code>. Here’s a basic example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>security delete-generic-password <span class="nt">-a</span> <span class="s2">"username"</span> <span class="nt">-s</span> <span class="s2">"service"</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">-a</code> specifies the account name.</li>
  <li><code class="language-plaintext highlighter-rouge">-s</code> specifies the service name.</li>
</ul>

<p>So, to remove the password for the account called graham on the service called gitlab, you would use:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>security delete-generic-password <span class="nt">-a</span> <span class="s2">"graham"</span> <span class="nt">-s</span> <span class="s2">"gitlab"</span>
</code></pre></div></div>

<p><a href="#top">Back to top</a></p>

<h2 id="using-a-secret">Using a secret</h2>

<p>OK, great, so we now have a way to store and retrieve secrets on our local machine. But how do we use these secrets in a script?</p>

<p>Well the <code class="language-plaintext highlighter-rouge">find-generic-password</code> command from <a href="#retrieving-a-password">above</a> is our friend here and we can either use it directly in a script or we can use it to set an environment variable in our shell profile.  Directly in a script would minimise the exposure of the secret, but it may be practical to be shared between scripts and/or users so setting an environment variable might be something to consider.</p>

<p>In a script it might look like:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="nb">local </span><span class="nv">GITLAB_TOKEN</span><span class="o">=</span><span class="si">$(</span>security find-generic-password <span class="nt">-a</span> <span class="s2">"graham"</span> <span class="nt">-s</span> <span class="s2">"gitlab"</span> <span class="nt">-w</span><span class="si">)</span>
</code></pre></div></div>

<p>In your shell profile it might look like:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># ~/.bashrc</span>
<span class="nb">export </span><span class="nv">GITLAB_TOKEN</span><span class="o">=</span><span class="si">$(</span>security find-generic-password <span class="nt">-a</span> <span class="s2">"graham"</span> <span class="nt">-s</span> <span class="s2">"gitlab"</span> <span class="nt">-w</span><span class="si">)</span>
</code></pre></div></div>

<p><a href="#top">Back to top</a></p>

<h2 id="not-leaving-your-password-in-command-history">Not leaving your password in command history</h2>

<p>These commands alone are great, and we’ve looked at how you might use them in your scripts and shell, but I’m sure the eagle-eyed and security conscious amongst you have noticed that just typing <code class="language-plaintext highlighter-rouge">security add-generic-password -a "graham" -s "gitlab" -w "your_password_here"</code> on the command line is likely to leave the value in your command history unless you take steps to remove it.</p>

<p>Sadly, from what I’ve found at least, <code class="language-plaintext highlighter-rouge">security</code> does not support prompting for the password interactively like <code class="language-plaintext highlighter-rouge">secret-tool</code> does on Linux. So what can we do? Well… I use <code class="language-plaintext highlighter-rouge">read</code> to capture the password interactively in a script without echoing it to the terminal, like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="nb">read</span> <span class="nt">-s</span> <span class="s2">"?Enter your password: "</span> PASSWORD
security add-generic-password <span class="nt">-a</span> <span class="s2">"graham"</span> <span class="nt">-s</span> <span class="s2">"gitlab"</span> <span class="nt">-w</span> <span class="s2">"</span><span class="nv">$PASSWORD</span><span class="s2">"</span> <span class="nt">-U</span>
</code></pre></div></div>

<p>If you’re on older macOS versions predating the move to <code class="language-plaintext highlighter-rouge">zsh</code> for the shell and you’re still using <code class="language-plaintext highlighter-rouge">bash</code> then you probably want <code class="language-plaintext highlighter-rouge">read -sp</code> instead, like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="nb">read</span> <span class="nt">-sp</span> <span class="s2">"Enter your password: "</span> PASSWORD
security add-generic-password <span class="nt">-a</span> <span class="s2">"graham"</span> <span class="nt">-s</span> <span class="s2">"gitlab"</span> <span class="nt">-w</span> <span class="s2">"</span><span class="nv">$PASSWORD</span><span class="s2">"</span> <span class="nt">-U</span>
</code></pre></div></div>

<p>I actually added a few helper commands to my shell rc file to do this for me, like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>add_secret<span class="o">()</span> <span class="o">{</span>
    <span class="nb">local </span><span class="nv">account</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
    <span class="nb">local </span><span class="nv">service</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span>
    <span class="nb">read</span> <span class="nt">-s</span> <span class="s2">"?Enter your password: "</span> secret
    security add-generic-password <span class="nt">-a</span> <span class="s2">"</span><span class="nv">$account</span><span class="s2">"</span> <span class="nt">-s</span> <span class="s2">"</span><span class="nv">$service</span><span class="s2">"</span> <span class="nt">-w</span> <span class="s2">"</span><span class="nv">$secret</span><span class="s2">"</span> <span class="nt">-U</span>
<span class="o">}</span>

get_secret<span class="o">()</span> <span class="o">{</span>
    <span class="nb">local </span><span class="nv">account</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
    <span class="nb">local </span><span class="nv">service</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span>
    security find-generic-password <span class="nt">-a</span> <span class="s2">"</span><span class="nv">$account</span><span class="s2">"</span> <span class="nt">-s</span> <span class="s2">"</span><span class="nv">$service</span><span class="s2">"</span> <span class="nt">-w</span>
<span class="o">}</span>

delete_secret<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">account</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
  <span class="nb">local </span><span class="nv">service</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span>
  security delete-generic-password <span class="nt">-a</span> <span class="s2">"</span><span class="nv">$account</span><span class="s2">"</span> <span class="nt">-s</span> <span class="s2">"</span><span class="nv">$service</span><span class="s2">"</span>
<span class="o">}</span>
</code></pre></div></div>

<p>I also add a quick <code class="language-plaintext highlighter-rouge">case</code> switch to help with my Linux muscle memory by adding a function called <code class="language-plaintext highlighter-rouge">secret-tool</code> which accepts the standard <code class="language-plaintext highlighter-rouge">store</code>, <code class="language-plaintext highlighter-rouge">lookup</code> and <code class="language-plaintext highlighter-rouge">clear</code> subcommands from the Linux tool. I also took the liberty to add a couple of others in case I forget the exact subcommands, so I get something like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>secret-tool<span class="o">()</span> <span class="o">{</span>
  <span class="k">case</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="k">in
    </span>store|add<span class="p">)</span>
      add_secret <span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$3</span><span class="s2">"</span>
      <span class="p">;;</span>
    lookup|get<span class="p">)</span>
      get_secret <span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$3</span><span class="s2">"</span>
      <span class="p">;;</span>
    clear|delete|remove<span class="p">)</span>
      delete_secret <span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$3</span><span class="s2">"</span>
      <span class="p">;;</span>
    <span class="k">*</span><span class="p">)</span>
      <span class="nb">echo</span> <span class="s2">"Usage: secret-tool {store|lookup|clear} account service"</span>
      <span class="k">return </span>1
      <span class="p">;;</span>
  <span class="k">esac</span>
<span class="o">}</span>
</code></pre></div></div>

<p>With that, I think we’re done. We have all the same tools for secure storing and managing secrets on the command line in macOS as we do on Linux.</p>

<p>If you’re like me and work across multiple platforms, having these consistent tools can make managing your secrets much easier. To help, please do check out my posts on <a href="/gnome-secrets/">Managing Secrets in Linux</a> and <a href="/windows-secrets/">Managing Secrets in Windows</a>.</p>

<p><a href="#top">Back to top</a></p>

<p>If this article helped inspire you please consider sharing this article with your friends and colleagues, or let me know via <a href="https://www.linkedin.com/in/wattsgraham/">LinkedIn</a> or <a href="https://twitter.com/Ginger_Graham">X / Twitter</a>.  If you have any ideas for further content you might like to see please let me know too.</p>

<p><a href="#top">Back to top</a></p>]]></content><author><name>Graham Watts</name><email>mail@grahamwatts.co.uk</email></author><category term="blog" /><category term="technical" /><category term="macos" /><category term="mac" /><category term="security" /><summary type="html"><![CDATA[A look at some tools to manage secrets when working on macOS]]></summary></entry><entry><title type="html">Logging in bash scripts</title><link href="https://www.grahamwatts.co.uk/bash-logging/" rel="alternate" type="text/html" title="Logging in bash scripts" /><published>2025-03-05T00:00:00+00:00</published><updated>2026-01-20T00:00:00+00:00</updated><id>https://www.grahamwatts.co.uk/bash-logging</id><content type="html" xml:base="https://www.grahamwatts.co.uk/bash-logging/"><![CDATA[<style>
  .page__hero--overlay {
    background-size: 100% auto !important;
    background-position: center !important;
  }
</style>

<p>I’ve spent many years working as a System Administrator and Support Engineer; the one thing I learned to value above anything else during those years was good logging. Good logging can make the difference between quickly finding an issue and spending hours debugging. Many of the more “formal” programming languages, such as Python, Java and C# have built-in, or easily importable, logging libraries that make it easy to create and standardise logs. However, when it comes to bash scripts, there are fewer tools available to help you craft and format log output. Yet many Sys Admins, DevOps Engineers and more use bash scripts every day to automate tasks, manage systems and more. Without good logs you’re fighting blind, or relying on the one guru in the office who just knows where to look. In this article I want to explore a few options and then share my opinionated view on what I think is a good approach to logging in bash scripts. I’ll even throw in a sample logging script that you can reference, or even import into your own scripts!</p>

<p>For those in a hurry, or who just want a solution, here are some quick links to the sections of this article:</p>

<ul>
  <li>Want full, customisable, logging module you can use, for free, in your bash scripts? Jump to <a href="#my-gift-to-you---a-logging-module-for-bash">My gift to you - a logging module for bash</a></li>
  <li>Need a quick function to help with logging? Check out <a href="#logging-functions---starting-our-journey-to-a-better-way">Logging functions - Starting our journey to a better way</a></li>
</ul>

<h2 id="why-log">Why log?</h2>

<p>Before we dive into the how, let’s take a moment to consider the why. Why should you log output from your bash scripts? There are a few reasons:</p>

<ol>
  <li><strong>Debugging</strong>: When something goes wrong, logs can help you understand what happened and why. They can help you identify the root cause of an issue and fix it.</li>
  <li><strong>Monitoring</strong>: Logs can help you monitor the health of your scripts. By looking at the logs, you can see how often the script is running, how long it takes to run, and whether it is producing any errors.</li>
  <li><strong>Auditing</strong>: Logs can help you keep track of what your scripts are doing. By looking at the logs, you can see who ran the script, when they ran it, and what the script did.</li>
  <li><strong>Compliance</strong>: In some cases, you may be required to keep logs of your scripts for compliance reasons. For example, if you are running a script that processes sensitive data, you may be required to keep logs of what the script did with that data.</li>
</ol>

<h2 id="why-do-i-care">Why do I care?</h2>

<p>I’m sure many people reading this are probably saying “OK, sure, in some cases logging is important, but I’m just writing a simple script to do X, I don’t need to log anything”. And you may be right. If you are writing a simple script that you run once and then forget about, logging may not be necessary. However, if you are writing a script that you plan to run regularly, or that will be run by other people, logging can be very useful. I would also say that for any instance where someone other than you may run the script or need to review the logging you output, or if you have to come back to it yourself some time later, that spending some time considering how and what you log is extremely valuable.</p>

<p>There is one area where I think logging is particularly important, and that is adding timestamps to your logs. Consider these scenarios:</p>

<ol>
  <li>Your script takes a long time to run, perhaps it exceeds some form of system timeout even. Without timestamps in your logs, or even logs at all (for those of you still not convinced) it becomes really hard to know where the script is spending its time. You likely resort to dumping <code class="language-plaintext highlighter-rouge">echo</code> statements throughout the script to try and figure out where it’s getting stuck. With timestamps on your logs and a good logging strategy, you can quickly identify where the script is spending its time and why.</li>
  <li>Your script produces an error. Maybe it collided with some other script or process; or maybe it’s a scheduled script and it fails at a certain interval for some reason. Without logs, and in particular without timestamps, it’s hard to really understand what happened, and drilling in to why it’s failing can take time and effort. With logs and timestamps, you can quickly identify things like does it always fail at a specific time, or the time it failed can help you correlate with other events on the system.</li>
</ol>

<p>Another thing you should consider is the consistency, or standardisation of the format of your logs. We have great tools like <a href="https://www.gnu.org/software/grep/">grep</a> and <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/select-string">Select-String</a> which can help us parse logs and industry tools like <a href="https://www.splunk.com/">Splunk</a> and <a href="https://www.elastic.co/">Elasticsearch</a> which can help us analyse logs. But these tools are only as good as the logs they are given. If your logs are inconsistent, or hard to parse, then these tools will be less effective. Consider, is the date before, or after the log level? Is the log level in square brackets, or round brackets? Is the script name included in the log message, or is it just the log level and message? These are all things that can make it harder to parse and analyse logs. Nevermind typos and variations like <code class="language-plaintext highlighter-rouge">warn</code> vs <code class="language-plaintext highlighter-rouge">warning</code> vs <code class="language-plaintext highlighter-rouge">WARN</code> vs <code class="language-plaintext highlighter-rouge">WARNING</code>, which is it that you are searching for? Can you be sure that you’ve found them all with a single <code class="language-plaintext highlighter-rouge">grep</code>?</p>

<p>For hints and tips on using regular expressions with <code class="language-plaintext highlighter-rouge">grep</code> and <code class="language-plaintext highlighter-rouge">Select-String</code> check out my article <a href="/regex/">here</a>.</p>

<p>Let’s be honest, other programming languages include logging for a reason. Whilst bash scripting is an extension of manual shell commands and is often seen as a quick and dirty get something done kind of language, it’s still a programming language and it’s still worth taking the time to craft good logs for anything that needs to be run more than once or by someone other than you.</p>

<p>I know many of my peers will push back and say that taking the time to craft good logs for “just a bash script” is a waste of time, that it’s hard to guarantee consistency and that it’s just not worth the effort. I disagree, as I’ve already said if others need to use your scripts or analyse the output then the value is there, but I do concede that it is hard to do right. Many of my scripts to date have had a variety of approaches and styles, so I’ve tried, failed, had success but with a lot of effort and otherwise been around the block on this one. So check out <a href="#my-gift-to-you---a-logging-module-for-bash">my gift to you</a> at the end of this article for a logging module that I’ve put together and will be using going forward. I hope that it will help you to craft better logs with less effort.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="the-basics">The basics</h2>

<p>Let’s start with the basics of what logging should look like, and what tools we have available to achieve this in bash scripts.</p>

<h3 id="understanding-log-levels">Understanding Log Levels</h3>

<p>When implementing logging in your bash scripts, it’s important to understand the different log levels and when to use each one. Proper use of log levels helps filter noise and focus on what’s important depending on the context.</p>

<h4 id="common-log-levels">Common Log Levels</h4>

<p>Here’s a breakdown of common log levels, from least to most severe:</p>

<ul>
  <li><strong>DEBUG</strong>: Detailed information, typically valuable only for diagnosing problems. These messages contain information that’s most useful when troubleshooting and should include variables, state changes, and decision points.
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>DEBUG] <span class="s2">"Processing file: </span><span class="nv">$filename</span><span class="s2"> with parameters: </span><span class="nv">$params</span><span class="s2">"</span>
</code></pre></div>    </div>
  </li>
  <li><strong>INFO</strong>: Confirmation that things are working as expected. These messages track the normal flow of execution and significant events in your script.
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>INFO] <span class="s2">"Backup process started for database: </span><span class="nv">$db_name</span><span class="s2">"</span>
</code></pre></div>    </div>
  </li>
  <li><strong>WARN</strong>: Indication that something unexpected happened, or that a problem might occur in the near future (e.g., filesystem running out of space). The script can continue running, but you should investigate.
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>WARN] <span class="s2">"Less than 10% disk space remaining on </span><span class="nv">$mount_point</span><span class="s2">"</span>
</code></pre></div>    </div>
  </li>
  <li><strong>ERROR</strong>: Due to a more serious problem, the script couldn’t perform some function. This doesn’t necessarily mean the script will exit, but it indicates that an operation failed.
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>ERROR] <span class="s2">"Failed to connect to database after 3 attempts"</span>
</code></pre></div>    </div>
  </li>
  <li><strong>FATAL</strong>: A severe error that will likely lead to the script aborting. Use this for critical failures that prevent the script from continuing execution.
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>FATAL] <span class="s2">"Required configuration file not found: </span><span class="nv">$config_file</span><span class="s2">"</span>
</code></pre></div>    </div>
  </li>
</ul>

<h4 id="when-to-use-each-level">When to Use Each Level</h4>

<ul>
  <li>Use <strong>DEBUG</strong> liberally during development but sparingly in production. It’s perfect for tracing execution flow and variable values.</li>
  <li>Use <strong>INFO</strong> to track normal operation milestones - script start/end, major function completions, or configuration loading.</li>
  <li>Use <strong>WARN</strong> when something unexpected happens but the script can recover or continue.</li>
  <li>Use <strong>ERROR</strong> when an operation fails but the script can still perform other tasks.</li>
  <li>Use <strong>FATAL</strong> only for critical failures that prevent the script from functioning at all.</li>
</ul>

<p>With proper log levels, both you and others can quickly filter logs to the appropriate level of detail needed for the task at hand - whether that’s real-time monitoring (INFO/WARN/ERROR) or detailed troubleshooting (DEBUG).</p>

<p><a href="#top">Back to top</a></p>

<h3 id="using-echo">Using <code class="language-plaintext highlighter-rouge">echo</code></h3>

<p>The most basic way to log output from a bash script is to use the <code class="language-plaintext highlighter-rouge">echo</code> command. This is a simple command that writes its arguments to standard output. For example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"This is a log message"</span>
</code></pre></div></div>

<p>Depending on your environment, this may, or may not actually end up in a log file. In the vast majority of cases, the output will not be written to a file and will only be visible on the terminal that ran the command or script. So, my advice is, assume that <code class="language-plaintext highlighter-rouge">echo</code> will <strong>not</strong> write to a file and that you need to do something else to capture the output.</p>

<p>And so…</p>

<h3 id="redirecting-output-to-a-file-with-">Redirecting output to a file with <code class="language-plaintext highlighter-rouge">&gt;</code></h3>

<p>If you want to write the output of a script to a file, you can use the <code class="language-plaintext highlighter-rouge">&gt;</code> operator to redirect the output to a file. For example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"This is a log message"</span> <span class="o">&gt;</span> /path/to/logfile.log
</code></pre></div></div>

<p>This will write the output of the <code class="language-plaintext highlighter-rouge">echo</code> command to the file <code class="language-plaintext highlighter-rouge">/path/to/logfile.log</code>. If the file does not exist, it will be created. If the file does exist, it will be overwritten. This is standard redirection behaviour.</p>

<p>Note however that the output is <strong>fully redirected</strong> and will not be visible on the terminal that ran the command or script.</p>

<h3 id="redirecting-output-to-a-file-with--1">Redirecting output to a file with <code class="language-plaintext highlighter-rouge">&gt;&gt;</code></h3>

<p>If you want to append to a file, rather than overwrite it, you can use the <code class="language-plaintext highlighter-rouge">&gt;&gt;</code> operator. For example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"This is a log message"</span> <span class="o">&gt;&gt;</span> /path/to/logfile.log
</code></pre></div></div>

<p>This will append the output of the <code class="language-plaintext highlighter-rouge">echo</code> command to the file <code class="language-plaintext highlighter-rouge">/path/to/logfile.log</code>. If the file does not exist, it will be created. If the file does exist, the output will be appended to the end of the file.</p>

<p>In most cases appending is the preferred method of logging as it allows you to keep a history of logs, rather than just the most recent output.</p>

<p>Note again that the output is fully redirected and will not be visible on the terminal that ran the command or script, in the same manner as the <code class="language-plaintext highlighter-rouge">&gt;</code> operator. They are related after all…</p>

<p>So what if we want to both <strong>see</strong> the output on the terminal and <strong>write</strong> it to a file? Well, that’s where <code class="language-plaintext highlighter-rouge">tee</code> comes in…</p>

<h3 id="using-tee-to-write-to-a-file-and-standard-output">Using <code class="language-plaintext highlighter-rouge">tee</code> to write to a file and standard output</h3>

<p>If you want to write to a file AND standard output at the same time, you can use the <code class="language-plaintext highlighter-rouge">tee</code> command. For example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"This is a log message"</span> | <span class="nb">tee</span> /path/to/logfile.log
</code></pre></div></div>

<p>This will write the output of the <code class="language-plaintext highlighter-rouge">echo</code> command to the file <code class="language-plaintext highlighter-rouge">/path/to/logfile.log</code> and to standard output.</p>

<p>For many years this was my approach to adding some form of logging to my scripts. You can save yourself some time by making the log path a variable, like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">LOGFILE</span><span class="o">=</span><span class="s2">"/path/to/logfile.log"</span>
<span class="nb">echo</span> <span class="s2">"This is a log message"</span> | <span class="nb">tee</span> <span class="nv">$LOGFILE</span>
<span class="nb">echo</span> <span class="s2">"This is another log message"</span> | <span class="nb">tee</span> <span class="nt">-a</span> <span class="nv">$LOGFILE</span>
</code></pre></div></div>

<h3 id="some-challenges">Some challenges</h3>

<p>This is all great, but let’s take a step back and think about this in more depth. I said in <a href="#why-do-i-care">Why do I care?</a> that adding timestamps to your logs is important for example. We can do that using the <code class="language-plaintext highlighter-rouge">date</code> command, but now our <code class="language-plaintext highlighter-rouge">echo</code> commands are getting a bit more complex:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">LOGFILE</span><span class="o">=</span><span class="s2">"/path/to/logfile.log"</span>
<span class="nb">echo</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">date</span><span class="si">)</span><span class="s2"> This is a log message"</span> | <span class="nb">tee</span> <span class="nt">-a</span> <span class="nv">$LOGFILE</span>
<span class="nb">echo</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">date</span><span class="si">)</span><span class="s2"> This is another log message"</span> | <span class="nb">tee</span> <span class="nt">-a</span> <span class="nv">$LOGFILE</span>
</code></pre></div></div>

<p>Maybe when you do this you find that the output from <code class="language-plaintext highlighter-rouge">date</code> is not quite what you want and you want to format it differently. Sure, we can do that, but our “log lines” are really starting to grow now:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">LOGFILE</span><span class="o">=</span><span class="s2">"/path/to/logfile.log"</span>
<span class="nb">echo</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">date</span> +<span class="s2">"%Y-%m-%d %H:%M:%S"</span><span class="si">)</span><span class="s2"> This is a log message"</span> | <span class="nb">tee</span> <span class="nt">-a</span> <span class="nv">$LOGFILE</span>
<span class="nb">echo</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">date</span> +<span class="s2">"%Y-%m-%d %H:%M:%S"</span><span class="si">)</span><span class="s2"> This is another log message"</span> | <span class="nb">tee</span> <span class="nt">-a</span> <span class="nv">$LOGFILE</span>
</code></pre></div></div>

<p>Maybe we want to add other information to our logs, like the name of the script that is running, or whether it is an informational message or a warning or error. We can add this but our “log lines” are getting really long now:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">LOGFILE</span><span class="o">=</span><span class="s2">"/path/to/logfile.log"</span>
<span class="nb">echo</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">date</span> +<span class="s2">"%Y-%m-%d %H:%M:%S"</span><span class="si">)</span><span class="s2"> [INFO] [</span><span class="si">$(</span><span class="nb">basename</span> <span class="nv">$0</span><span class="si">)</span><span class="s2">] This is a log message"</span> | <span class="nb">tee</span> <span class="nt">-a</span> <span class="nv">$LOGFILE</span>
<span class="nb">echo</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">date</span> +<span class="s2">"%Y-%m-%d %H:%M:%S"</span><span class="si">)</span><span class="s2"> [WARN] [</span><span class="si">$(</span><span class="nb">basename</span> <span class="nv">$0</span><span class="si">)</span><span class="s2">] This is another log message"</span> | <span class="nb">tee</span> <span class="nt">-a</span> <span class="nv">$LOGFILE</span>
</code></pre></div></div>

<p>Here we also start to see where we can loose standardisation. Is it <code class="language-plaintext highlighter-rouge">[WARN]</code> or <code class="language-plaintext highlighter-rouge">[WARNING]</code>? Or <code class="language-plaintext highlighter-rouge">[INFO]</code> or <code class="language-plaintext highlighter-rouge">[Info]</code>? If you hand type every line, it’s easy to make a mistake, and if you copy paste maybe you forget to update the log line with the crucial piece of information that you need. I’ve done this myself countless times; “why is process X starting again?” only to go back through my script and find, “Oh, I copied the log line from process X and forgot to update it to now say process Y has started instead”.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="what-about-using--x-in-my-bash-scripts">What about using <code class="language-plaintext highlighter-rouge">-x</code> in my bash scripts?</h2>

<p>The <code class="language-plaintext highlighter-rouge">-x</code> option can be a helpful tool in tracking what your script is doing and when. It will print each command that is executed to standard output before it is executed. For example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="nb">set</span> <span class="nt">-x</span>

<span class="nb">echo</span> <span class="s2">"This is a log message"</span>
<span class="nb">echo</span> <span class="s2">"This is another log message"</span>
</code></pre></div></div>

<p>When you run this script, you will see the following output:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+ <span class="nb">echo</span> <span class="s1">'This is a log message'</span>
This is a log message
+ <span class="nb">echo</span> <span class="s1">'This is another log message'</span>
This is another log message
</code></pre></div></div>

<p>Now, I don’t know about you, but already I’m finding this a bit noisy, having every command printed out and then the output, it’s a bit much if you ask me. I absolutely agree that it can be helpful for debugging and then remove it for production, but it’s not a real logging solution. Try turning this on for a long script and then grepping through to find what happened, let alone what time it happened and I think you’ll agree that it’s not a great solution for logging.</p>

<p>For me, logging really should track what we need to know, when we need to know it and in a consistent format that we can easily parse and analyse. Often seeing every command is just too much noise and not enough signal. Your mileage may vary…</p>

<p><a href="#top">Back to top</a></p>

<h2 id="logging-functions---starting-our-journey-to-a-better-way">Logging functions - Starting our journey to a better way</h2>

<p>OK, we have some tools to use and some challenges to overcome. Let’s accept that we want to be better than dozens of <code class="language-plaintext highlighter-rouge">echo</code> and <code class="language-plaintext highlighter-rouge">tee</code> commands in our scripts and start to think about how we can improve our logging. A more elegant approach is to define a dedicated logging function within your script. This function can take care of adding timestamps, script names, log levels, and anything else we want to add to our logs. Here’s an example of a simple logging function:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>log<span class="o">()</span> <span class="o">{</span>
    <span class="nb">local </span><span class="nv">log_level</span><span class="o">=</span><span class="nv">$1</span>  <span class="c"># A string representing the log level provided by the user when calling the function</span>
    <span class="nb">local </span><span class="nv">message</span><span class="o">=</span><span class="nv">$2</span>  <span class="c"># A string representing the message provided by the user when calling the function</span>
    <span class="nb">local </span><span class="nv">script_name</span><span class="o">=</span><span class="si">$(</span><span class="nb">basename</span> <span class="nv">$0</span><span class="si">)</span>  <span class="c"># The name of the script that is running</span>
    <span class="nb">local </span><span class="nv">timestamp</span><span class="o">=</span><span class="si">$(</span><span class="nb">date</span> +<span class="s2">"%Y-%m-%d %H:%M:%S"</span><span class="si">)</span>  <span class="c"># The current date and time at the time the function is called</span>
    <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$timestamp</span><span class="s2"> [</span><span class="nv">$log_level</span><span class="s2">] [</span><span class="nv">$script_name</span><span class="s2">] </span><span class="nv">$message</span><span class="s2">"</span> | <span class="nb">tee</span> <span class="nt">-a</span> <span class="nv">$LOGFILE</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Now we can call this function to log messages in our script. For example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">LOGFILE</span><span class="o">=</span><span class="s2">"/path/to/logfile.log"</span>
log <span class="s2">"INFO"</span> <span class="s2">"This is a log message"</span>
log <span class="s2">"WARN"</span> <span class="s2">"This is another log message"</span>
</code></pre></div></div>

<p>The output would look like this:</p>

<pre><code class="language-log">2021-10-01 12:00:00 [INFO] [script.sh] This is a log message
2021-10-01 12:00:01 [WARN] [script.sh] This is another log message
</code></pre>

<p>Immediately this is better. We only need to worry about the form of the log messages once. From then on out we can just call <code class="language-plaintext highlighter-rouge">log</code> with the log level and message that we want and the function does all the heavy lifting for us. If we want to make changes to the format of the log messages, we only need to change the <code class="language-plaintext highlighter-rouge">log</code> function, rather than every <code class="language-plaintext highlighter-rouge">echo</code> command in our script.</p>

<p>This is a great step forward if you ask me, and for a long time it’s been my go to solution. But here’s a challenge I came across that I want you to think about. You’re writing a script that is going to be used on a recurring scheduled task, during development and troubleshooting, you want to include extra logging, debug logging if you will, to help you understand what’s going on. But you don’t want to include this debug logging in production, it’s just too noisy and not needed. How do you handle this?</p>

<p>My solution was to have an environment variable, or more commonly a command line argument for <code class="language-plaintext highlighter-rouge">-d</code> or similar, that would indicate I wanted debug logging. This would be used to set a variable in the script that would then be used to determine if debug logging should be included. So far, so good. However to implement this you now need every debug log line to be wrapped in an <code class="language-plaintext highlighter-rouge">if</code> statement, like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$DEBUG</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"true"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span>log <span class="s2">"DEBUG"</span> <span class="s2">"This is a debug message"</span>
<span class="k">fi</span>
</code></pre></div></div>

<p>Scale this across a moderately long script and you’re adding a lot of extra noise and processing to your script. You’re adding a lot of boilerplate code that obscures the productive lines of your script. Lots of extra logic that needs to be seen by a reader and acknowledged as not part of the main script logic. Now try coming back to your script 6 months later, or explain it to someone else as part of a knowledge transfer and it becomes hard. I know, I’ve done it. The conversation ends up something like “don’t worry about all these extra lines, they’re just for debug logging, you can ignore them.” This approach does work, and where nobody else is opening up your script or they understand the approach it does work well. But it’s not ideal.</p>

<p>So… what’s the solution? Well, I’ve been working on a logging module for bash scripts that I think solves this problem. It’s a bit more complex than the simple <code class="language-plaintext highlighter-rouge">log</code> function above, but it’s also more powerful and flexible, you can find it <a href="#my-gift-to-you---a-logging-module-for-bash">below</a>.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="what-about-logger">What about <code class="language-plaintext highlighter-rouge">logger</code>?</h2>

<p>Before we get to my solution, let’s take a moment to explore another tool we have called <code class="language-plaintext highlighter-rouge">logger</code>. <code class="language-plaintext highlighter-rouge">logger</code> is a command-line tool that allows you to send messages to the system log. This can be useful for logging messages from your bash scripts to the system log, which can then be viewed using tools like <code class="language-plaintext highlighter-rouge">journalctl</code> on Linux systems. For example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>logger <span class="s2">"This is a log message"</span>
</code></pre></div></div>

<p>We can then inspect the system log using <code class="language-plaintext highlighter-rouge">journalctl</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>journalctl | <span class="nb">grep</span> <span class="s2">"This is a log message"</span>
</code></pre></div></div>

<p>This can be a useful tool for logging messages from your bash scripts, especially if you want to centralise your logs in the system log. In many ways I think <code class="language-plaintext highlighter-rouge">logger</code> is a great tool. Personally I’m a fan of using the system log as it tightly integrates with everything else happening on a system. However, not everyone agrees and many engineers want, or expect, to find a dedicated log file for their script or application. If you want your logs somewhere other than the system log then you will need a custom logging solution such as the <a href="#logging-functions---starting-our-journey-to-a-better-way">function</a> above or the <a href="#my-gift-to-you---a-logging-module-for-bash">logging module</a> I’ve put together, below.</p>

<p>Before we get to my custom solution though, let’s explore <code class="language-plaintext highlighter-rouge">logger</code> a bit more.</p>

<h3 id="using-logger-with-log-levels">Using <code class="language-plaintext highlighter-rouge">logger</code> with log levels</h3>

<p><code class="language-plaintext highlighter-rouge">logger</code> also supports log levels, based on syslog levels. Here’s how you can use <code class="language-plaintext highlighter-rouge">logger</code> with different log levels:</p>

<ul>
  <li><strong>DEBUG</strong>: Use the <code class="language-plaintext highlighter-rouge">-t</code> option to specify the tag, and the <code class="language-plaintext highlighter-rouge">-p</code> option to specify the priority. For example:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>logger <span class="nt">-t</span> script.sh <span class="nt">-p</span> user.debug <span class="s2">"This is a debug message"</span>
</code></pre></div>    </div>
  </li>
  <li><strong>INFO</strong>: Use the <code class="language-plaintext highlighter-rouge">-t</code> option to specify the tag, and the <code class="language-plaintext highlighter-rouge">-p</code> option to specify the priority. For example:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>logger <span class="nt">-t</span> script.sh <span class="nt">-p</span> user.info <span class="s2">"This is an informational message"</span>
</code></pre></div>    </div>
  </li>
  <li><strong>WARN</strong>: Use the <code class="language-plaintext highlighter-rouge">-t</code> option to specify the tag, and the <code class="language-plaintext highlighter-rouge">-p</code> option to specify the priority. For example:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>logger <span class="nt">-t</span> script.sh <span class="nt">-p</span> user.warning <span class="s2">"This is a warning message"</span>
</code></pre></div>    </div>
  </li>
  <li><strong>ERROR</strong>: Use the <code class="language-plaintext highlighter-rouge">-t</code> option to specify the tag, and the <code class="language-plaintext highlighter-rouge">-p</code> option to specify the priority. For example:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>logger <span class="nt">-t</span> script.sh <span class="nt">-p</span> user.err <span class="s2">"This is an error message"</span>
</code></pre></div>    </div>
  </li>
  <li><strong>FATAL</strong>: Use the <code class="language-plaintext highlighter-rouge">-t</code> option to specify the tag, and the <code class="language-plaintext highlighter-rouge">-p</code> option to specify the priority. For example:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>logger <span class="nt">-t</span> script.sh <span class="nt">-p</span> user.crit <span class="s2">"This is a critical message"</span>
</code></pre></div>    </div>
  </li>
</ul>

<p>We can then inspect the journal log using <code class="language-plaintext highlighter-rouge">journalctl</code> for specific level(s):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>journalctl <span class="nt">-p</span> user.debug

<span class="c"># Or multiple levels</span>
journalctl <span class="nt">-p</span> user.debug <span class="nt">-p</span> user.info <span class="c"># Returns any log level from debug or info</span>

journalctl <span class="nt">-p</span> warning..crit <span class="c"># Returns any log level from warning to critical</span>
</code></pre></div></div>

<h3 id="tagging-your-logs">Tagging your logs</h3>

<p>The examples above actually included something else, the <code class="language-plaintext highlighter-rouge">-t</code> flag which applies a tag to a log message. This can be useful for filtering logs from a specific script or application. For example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>logger <span class="nt">-t</span> script.sh <span class="nt">-p</span> user.info <span class="s2">"This is an informational message"</span>
</code></pre></div></div>

<p>You can then filter logs from this script using the tag:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>journalctl <span class="nt">-t</span> script.sh
</code></pre></div></div>

<h3 id="tags-and-levels-together-in-the-journal">Tags and Levels together in the journal</h3>

<p>Let’s put these two techniques together and see what it looks like</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> logger <span class="nt">-t</span> demo <span class="nt">-p</span> user.debug <span class="s2">"Hello World"</span>

<span class="o">&gt;</span> journalctl <span class="nt">-t</span> demo

Mar 05 15:19:42 gw-fw13-01 demo[19621]: Hello World

<span class="o">&gt;</span> logger <span class="nt">-t</span> demo <span class="nt">-p</span> user.err <span class="s2">"Goodbye Cruel World"</span>

<span class="o">&gt;</span> journalctl <span class="nt">-t</span> demo  

Mar 05 15:19:42 gw-fw13-01 demo[19621]: Hello World
Mar 05 15:20:40 gw-fw13-01 demo[20310]: Goodbye Cruel World

<span class="o">&gt;</span> journalctl <span class="nt">-t</span> demo <span class="nt">-p</span> err 

Mar 05 15:20:40 gw-fw13-01 demo[20310]: Goodbye Cruel World
</code></pre></div></div>

<p>What my blog cannot easily show you is that on a modern shell with colour support you will see the log levels in different colours. This can be really helpful when scanning through logs to quickly identify the level of a log message. For example the error log level is often red. Try it for yourself and see.</p>

<h2 id="my-gift-to-you---a-logging-module-for-bash">My gift to you - a logging module for bash</h2>

<p>OK! We’ve looked at logging using common console tools and we’ve talked about logger. It’s all a lot to remember isn’t it? So I’ve put together a logging module that is available on GitHub that you can use in your own scripts. You can find the module <a href="https://github.com/GingerGraham/bash-logger">here</a> including full documentation on how to use it and a series of demo scripts that showcase some of the use cases. I hope that you find it useful and that it helps you to craft better logs with less effort.</p>

<p>The module includes the following features:</p>

<ul>
  <li>Simple functions for each logging level such as <code class="language-plaintext highlighter-rouge">log_info</code>, and <code class="language-plaintext highlighter-rouge">log_error</code> which only require the message to be logged.
    <ul>
      <li>These functions make it immediately clear what the log level is and reduce the boilerplate code in your script.</li>
    </ul>
  </li>
  <li>A <code class="language-plaintext highlighter-rouge">log_debug</code> function that only logs messages if debug logging is enabled.
    <ul>
      <li>You can determine what level of logging you want to include in your logs, and only include debug messages when needed.</li>
      <li>It even provides an option to adjust logging level at runtime, so you can enable, or disable debug logging for a specific function or block without restarting your script.</li>
    </ul>
  </li>
  <li>Colourised console output for different log levels.
    <ul>
      <li>This makes it easier to see at a glance what level a log message is.</li>
    </ul>
  </li>
  <li>Customisable log format.
    <ul>
      <li>You can customise the format of your log messages to include whatever information you need.</li>
    </ul>
  </li>
  <li>Customisable log file.
    <ul>
      <li>You specify where you want the log, or if you want one at all.
        <ul>
          <li>You can just use the module to log to console if you prefer and benefit from the colourised output without writing to a file.</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Optional time zone support.
    <ul>
      <li>You can specify whether logs should be UTC or your local time zone.</li>
    </ul>
  </li>
  <li>Optionally send logs to the system log/journal as well</li>
</ul>

<h3 id="getting-started-with-the-logging-module">Getting started with the logging module</h3>

<p>To get started you just need to download <code class="language-plaintext highlighter-rouge">logging.sh</code> from my provided <a href="https://github.com/GingerGraham/bash-logger/releases/latest">GitHub repository</a> and <code class="language-plaintext highlighter-rouge">source</code> it in your script. Here’s an example of how you might use it:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="c"># Source the logging module</span>
<span class="nb">source </span>logging.sh

<span class="c"># Set the log file</span>
<span class="nv">LOGFILE</span><span class="o">=</span><span class="s2">"/path/to/logfile.log"</span>

<span class="c"># Initialise the logging module</span>
init_logger <span class="nt">--log</span> <span class="nv">$LOGFILE</span> <span class="nt">--level</span> INFO <span class="nt">--utc</span>  <span class="c"># Will initialise the logger to write to the log file, with INFO level logging and in UTC</span>

<span class="c"># Log some messages</span>
log_info <span class="s2">"This is an informational message"</span>
log_warn <span class="s2">"This is a warning message"</span>
log_error <span class="s2">"This is an error message"</span>
log_debug <span class="s2">"This is a debug message"</span>
</code></pre></div></div>

<p><strong>Note 1:</strong> The <code class="language-plaintext highlighter-rouge">init_logger</code> function should be called before any other logging functions are called to configure key options such as writing to log file or to the journal. It sets up the logger with the specified log file, log level, and time zone and other configurations.</p>

<p><strong>Note 2:</strong> You do not need to locate <code class="language-plaintext highlighter-rouge">logging.sh</code> in the same directory as your script. You can place it anywhere you like and <code class="language-plaintext highlighter-rouge">source</code> it with the <strong>full path</strong> (using relative paths can sometimes generate unexpected behaviours).</p>

<p><img src="../../assets/gif/code-demos/simple_logging_demo-info.gif" alt="Logging Module Demo" /></p>

<ul>
  <li>This is a simple demo of the logging module in action. You can see that the log messages are colourised and include the log level, script name and timestamp. The log messages are also written to the specified log file and the journal.</li>
</ul>

<h3 id="adding-debug-support-to-your-script">Adding Debug support to your script</h3>

<p>You’ve implemented the logging module and you’re now logging messages at different levels. But you want to add debug logging to your script, and you want to be able to enable or disable it at runtime. Here’s how you can do that:</p>

<ol>
  <li>Add a <code class="language-plaintext highlighter-rouge">-d</code> or <code class="language-plaintext highlighter-rouge">--debug</code> command line argument to your script.</li>
  <li>Include a variable in your script for <code class="language-plaintext highlighter-rouge">LOG_LEVEL</code> that is set to <code class="language-plaintext highlighter-rouge">INFO</code> by default.</li>
  <li>If the <code class="language-plaintext highlighter-rouge">-d</code> or <code class="language-plaintext highlighter-rouge">--debug</code> argument is provided, set <code class="language-plaintext highlighter-rouge">LOG_LEVEL</code> to <code class="language-plaintext highlighter-rouge">DEBUG</code>.</li>
  <li>Call the <code class="language-plaintext highlighter-rouge">init_logger</code> function with the <code class="language-plaintext highlighter-rouge">LOG_LEVEL</code> variable, e.g. <code class="language-plaintext highlighter-rouge">init_logger --log $LOGFILE --level $LOG_LEVEL</code>.</li>
</ol>

<p>Let’s see that in action:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="c"># Source the logging module</span>
<span class="nb">source </span>logging.sh

<span class="c"># Set the log file</span>
<span class="nv">LOGFILE</span><span class="o">=</span><span class="s2">"/path/to/logfile.log"</span>

<span class="c"># Set the log level</span>
<span class="nv">LOG_LEVEL</span><span class="o">=</span><span class="s2">"INFO"</span>

<span class="c"># Check for debug flag</span>
<span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"-d"</span> <span class="o">||</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"--debug"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">LOG_LEVEL</span><span class="o">=</span><span class="s2">"DEBUG"</span>
<span class="k">fi</span>

<span class="c"># Initialise the logging module</span>
init_logger <span class="nt">--log</span> <span class="nv">$LOGFILE</span> <span class="nt">--level</span> <span class="nv">$LOG_LEVEL</span>

<span class="c"># Log some messages</span>
log_info <span class="s2">"This is an informational message"</span>
log_warn <span class="s2">"This is a warning message"</span>
log_error <span class="s2">"This is an error message"</span>
log_debug <span class="s2">"This is a debug message"</span>  <span class="c"># Only prints to console and/or log file if debug logging is enabled</span>
</code></pre></div></div>

<p>Now you can run your script with the <code class="language-plaintext highlighter-rouge">-d</code> or <code class="language-plaintext highlighter-rouge">--debug</code> argument to enable debug logging. For example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Debug use</span>
./script.sh <span class="nt">--debug</span>

<span class="c"># Normal use</span>
./script.sh
</code></pre></div></div>

<p>That’s it! It’s as simple as that! You can now include debug logging in your script and enable or disable it at runtime. Obviously you can have a more complex argument setup for your script with <code class="language-plaintext highlighter-rouge">getopts</code> or similar depending on your needs, but this is a simple example to get you started.</p>

<p><img src="../../assets/gif/code-demos/simple_logging_demo-debug.gif" alt="Logging Module Debug Demo" /></p>

<ul>
  <li>This is a simple demo of the logging module in action with debug logging enabled. You can see that the debug log messages are colourised and include the log level, script name and timestamp. The log messages are also written to the specified log file and the journal.</li>
</ul>

<h2 id="what-else-should-i-think-about">What else should I think about?</h2>

<p>There are a few other things you should consider when implementing logging in your bash scripts:</p>

<ul>
  <li><strong>Log content</strong>: What information do you need to include in your logs? Or, what information should you <strong>NOT</strong> include in your logs?</li>
  <li><strong>Log rotation</strong>: If your log files get too large, they can become difficult to manage. Consider implementing log rotation to keep your log files at a manageable size.</li>
  <li><strong>Log retention</strong>: How long do you need to keep your log files? Consider implementing a log retention policy to automatically delete old log files.</li>
</ul>

<h3 id="log-content">Log content</h3>

<p>As with any logging, we should spend some time considering what information we need to include in our logs, and what information absolutely should not be included. Here are a few things to consider:</p>

<ul>
  <li><strong>Sensitive information</strong>: Be careful not to include sensitive information or <a href="https://www.security.org/identity-theft/what-is-pii/">Personally Identifiable Information (PII)</a> in your logs, such as passwords, API keys, or other credentials. If you need to log this information, consider obfuscating it or using a secure logging solution.
    <ul>
      <li>If, for some reason, you need to verify sensitive information during debugging, consider options such as:
        <ul>
          <li>Only sending the logs to a secure location with an very short retention policy</li>
          <li>Only logging sensitive information to the console and not to a file
            <ul>
              <li>Ensure that your console session itself is not logging</li>
              <li>The provided <a href="#my-gift-to-you---a-logging-module-for-bash">logging module</a> includes a <code class="language-plaintext highlighter-rouge">log_sensitive</code> function that will only log to the console and not to a file</li>
            </ul>
          </li>
        </ul>
      </li>
      <li>Remember that logs can be read by anyone with access to the log files, so be careful what you include in your logs.</li>
    </ul>
  </li>
  <li><strong>Error messages</strong>: Be sure to include detailed error messages in your logs to help with troubleshooting. Include any error codes, stack traces, or other useful information that can help you identify the root cause of an issue.</li>
  <li><strong>Contextual information</strong>: Include any contextual information that can help you understand what’s happening in your script. This might include the script name, the function name, the line number, or any other information that can help you trace the flow of execution
    <ul>
      <li>The provided <a href="#my-gift-to-you---a-logging-module-for-bash">logging module</a> includes the script name but not the function name or line number. This is something you could add if you need it.</li>
    </ul>
  </li>
  <li><strong>Timestamps</strong>: Include timestamps in your logs to help you understand when events occurred. This can be useful for correlating events across different logs and understanding the sequence of events.</li>
  <li><strong>Log levels</strong>: Include <a href="#common-log-levels">log levels</a> in your logs to help you filter and categorise log messages. This can help you focus on what’s important and filter out what’s not.</li>
</ul>

<h3 id="log-rotation">Log rotation</h3>

<p>Log rotation is the process of archiving old log files and creating new log files to keep the log files at a manageable size. There are many tools available to help with log rotation, such as <code class="language-plaintext highlighter-rouge">logrotate</code> on Linux systems. You can also implement log rotation in your bash scripts by checking the size of the log file and creating a new log file when it reaches a certain size. Here’s an example of how you might implement log rotation in your script:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="nv">LOGFILE</span><span class="o">=</span><span class="s2">"/path/to/logfile.log"</span>

<span class="c"># Check the size of the log file</span>
<span class="nv">log_size</span><span class="o">=</span><span class="si">$(</span><span class="nb">du</span> <span class="nt">-h</span> <span class="nv">$LOGFILE</span> | <span class="nb">awk</span> <span class="s1">'{print $1}'</span><span class="si">)</span>

<span class="c"># If the log file is larger than 1MB, create a new log file</span>
<span class="k">if</span> <span class="o">[</span> <span class="k">${</span><span class="nv">log_size</span><span class="p">%?</span><span class="k">}</span> <span class="nt">-gt</span> 1 <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">mv</span> <span class="nv">$LOGFILE</span> <span class="nv">$LOGFILE</span>.<span class="si">$(</span><span class="nb">date</span> +<span class="s2">"%Y%m%d%H%M%S"</span><span class="si">)</span>.log
<span class="k">fi</span>

<span class="c"># Initialise the logging module</span>
init_logger <span class="nt">--log</span> <span class="nv">$LOGFILE</span> <span class="nt">--level</span> INFO
</code></pre></div></div>

<p>Alternatively you can configure <a href="https://linuxconfig.org/logrotate"><code class="language-plaintext highlighter-rouge">logrotate</code></a> to manage your log files for you. This is a more robust solution and is recommended for production systems.</p>

<h3 id="log-retention">Log retention</h3>

<p>Log retention is the process of deleting old log files to free up disk space. You should consider how long you need to keep your log files and implement a log retention policy to automatically delete old log files, especially if you are working in secure or regulated environments. Also, good green engineering principles say that we should not consume ever more disk space with logs that are no longer needed. Here’s an example of how you might implement log retention in your script:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="nv">LOGFILE</span><span class="o">=</span><span class="s2">"/path/to/logfile.log"</span>

<span class="c"># Delete log files older than 30 days</span>
find /path/to/logs <span class="nt">-name</span> <span class="s2">"*.log"</span> <span class="nt">-mtime</span> +30 <span class="nt">-exec</span> <span class="nb">rm</span> <span class="o">{}</span> <span class="se">\;</span>
</code></pre></div></div>

<p>This script will delete log files in the <code class="language-plaintext highlighter-rouge">/path/to/logs</code> directory that are older than 30 days. You can adjust the number of days as needed.</p>

<p>Alternatively you can configure <code class="language-plaintext highlighter-rouge">logrotate</code> (See above) to manage your log files for you. This is a more robust solution and is recommended for production systems.</p>

<h2 id="wrapping-up">Wrapping up</h2>

<p>I hope that you found this article useful, and in particular I hope that you find the <a href="#my-gift-to-you---a-logging-module-for-bash">logging module</a> that I’ve put together can help you to craft better logs with less effort. I’m passionate about providing engineers with more information to empower better decisions and quicker problem resolution. I believe that good logging is a key part of that, and I hope that this module can help you to achieve that.</p>

<p><a href="#top">Back to top</a></p>

<p>If this article helped inspire you please consider sharing this article with your friends and colleagues, or let me know via <a href="https://www.linkedin.com/in/wattsgraham/">LinkedIn</a> or <a href="https://twitter.com/Ginger_Graham">X / Twitter</a>.  If you have any ideas for further content you might like to see please let me know too.</p>

<p><a href="#top">Back to top</a></p>]]></content><author><name>Graham Watts</name><email>mail@grahamwatts.co.uk</email></author><category term="blog" /><category term="technical" /><category term="linux" /><category term="devops" /><category term="development" /><summary type="html"><![CDATA[Advocating for implementing logging in bash scripts and a look at some tools to help us do it]]></summary></entry><entry><title type="html">Understanding Certificates And Public Key Infrastructure</title><link href="https://www.grahamwatts.co.uk/pki-101/" rel="alternate" type="text/html" title="Understanding Certificates And Public Key Infrastructure" /><published>2024-11-11T00:00:00+00:00</published><updated>2024-11-13T15:30:00+00:00</updated><id>https://www.grahamwatts.co.uk/pki-101</id><content type="html" xml:base="https://www.grahamwatts.co.uk/pki-101/"><![CDATA[<p>In this article I want to explore, and demystify, the subject of Public Key Infrastructure (PKI) and certificates.</p>

<p>I’ve made a career out of taking on the challenges that nobody else wants to embrace. One of the most common subjects I’ve seen fellow professionals steer away from is typically referred to as “certificates” but what we’re really talking about is Public Key Infrastructure (PKI). Honestly, it’s not as complicated as it seems, but my theory is that it seems hard because the nature of certificates is that they tend to last for long enough that you forget how you got them working/renewed last time, and that scares people away. I’m not going to get into the deep technical details of PKI in this article, I’ll save that for another time. What we’ll explore here is the concepts and high level processes that go into setting up and working with PKI, so that you can understand the important parts of the process and be able to ask the right questions when you need to.</p>

<p>So, let’s dive in and start making you a PKI expert!</p>

<h2 id="what-is-pki">What is PKI?</h2>

<p>Let’s start with answering the question “What is PKI?”.</p>

<p>We’ll get to the “key” part of PKI in a moment, but let’s start by saying that <a href="https://en.wikipedia.org/wiki/Public-key_cryptography">Public Key Infrastructure (PKI)</a> is a critical component of modern computing to help us ensure the security, integrity, and privacy of data and communications. It is built from a set of technologies and processes that come together as a framework to create and manage digital certificates. These certificates can be used to secure communications, authenticate users, and ensure the integrity of data amongst the many potential uses.</p>

<p>The core concepts to understanding PKI are hierarchy and trust. In short, certificates are issued by an authority, a certificate authority (CA). If you trust the CA, you can trust the certificates it issues. Think of it like a friend recommending a restaurant to you; if you trust your friend, you’re more likely to trust the restaurant they recommend. The CA is your friend, the certificate is the recommendation, and you are the client.</p>

<p>Where it does start to get more complicated in when we start to think about hierarchy. The reality of modern computing is that we need to create abstractions to apply management and security strategies. For example, if PKI is all about trust then the certificate authority is a prime target for attack; so we commonly look to defend our Root CAs by doing things like turning them off, or running them offline. But we still need to do things, like issue new certificates, so we create Intermediate CAs to do that work for us. This creates a hierarchy of trust, where the Root CA is at the top, and Intermediate CAs are below it. Again, think of your friend and the restaurant. Your fiend has told you that you can trust the restaurant, so now when the waiter recommends a dish you’re more likely to trust that too.</p>

<p>A common PKI hierarchy might look like this:</p>

<p><img class="mermaid" src="https://mermaid.ink/svg/eyJjb2RlIjoiZ3JhcGggVERcblJvb3RbXCJSb290IENlcnRpZmljYXRlIEF1dGhvcml0eVwiXVxuUG9saWN5W1wiUG9saWN5IENlcnRpZmljYXRlIEF1dGhvcml0eVwiXVxuSXNzdWUxW1wiSXNzdWluZzxici8-Q2VydGlmaWNhdGUgQXV0aG9yaXR5XCJdXG5Jc3N1ZTJbXCJJc3N1aW5nPGJyLz5DZXJ0aWZpY2F0ZSBBdXRob3JpdHlcIl1cbkVuZDFbXCJDbGllbnQgQ2VydGlmaWNhdGVzXCJdXG5FbmQyW1wiQ2xpZW50IENlcnRpZmljYXRlc1wiXVxuRW5kM1tcIkNsaWVudCBDZXJ0aWZpY2F0ZXNcIl1cbkVuZDRbXCJDbGllbnQgQ2VydGlmaWNhdGVzXCJdXG4lJS1cblJvb3QgLS0tIFBvbGljeVxuUG9saWN5IC0tLSBJc3N1ZTFcblBvbGljeSAtLS0gSXNzdWUyXG5Jc3N1ZTEgLS0tIEVuZDFcbklzc3VlMSAtLS0gRW5kMlxuSXNzdWUyIC0tLSBFbmQzXG5Jc3N1ZTIgLS0tIEVuZDRcbiUlLVxuY2xhc3NEZWYgcm9vdCBmaWxsOiNmZjk5NjYsc3Ryb2tlOiNmZmZmZmYsc3Ryb2tlLXdpZHRoOjRweDtcbmNsYXNzRGVmIHBvbGljeSBmaWxsOiM2NmNjZmYsc3Ryb2tlOiNmZmZmZmYsc3Ryb2tlLXdpZHRoOjRweDtcbmNsYXNzRGVmIGlzc3VpbmcgZmlsbDojOTBFRTkwLHN0cm9rZTojZmZmZmZmLHN0cm9rZS13aWR0aDo0cHg7XG5jbGFzc0RlZiBlbmRFbnRpdHkgZmlsbDojZmZmZmZmLHN0cm9rZTojZmZmZmZmLHN0cm9rZS13aWR0aDo0cHgsY29sb3I6IzAwMDAwMDtcbiUlLVxuY2xhc3MgUm9vdCByb290O1xuY2xhc3MgUG9saWN5IHBvbGljeTtcbmNsYXNzIElzc3VlMSxJc3N1ZTIgaXNzdWluZztcbmNsYXNzIEVuZDEsRW5kMixFbmQzLEVuZDQgZW5kRW50aXR5O1xuJSUtXG5saW5rU3R5bGUgZGVmYXVsdCBzdHJva2U6I2ZmZmZmZixzdHJva2Utd2lkdGg6MnB4OyIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In19" /></p>

<p>Don’t worry too much about the specifics of the hierarchy just yet, we’ll get to that in a moment. For now, just understand that we have this hierarchy and that we need to trust the hierarchy (in some way, again we’ll get to that in a moment) to trust the certificates and therefore the services using them.</p>

<p><a href="#top">Back to top</a></p>

<h3 id="the-key-part-of-pki">The “key” part of PKI</h3>

<p>So, what about the “key” part of PKI?</p>

<p>Well, at it’s heart PKI is an implementation of cryptography (no not “crypto” and cryptocurrencies, although they are related). Cryptography is the science of securing communications and data. In the context of PKI, we’re talking about asymmetric cryptography. Asymmetric cryptography uses a pair of keys, a public key and a private key, to encrypt and decrypt data. The public key can be shared with anyone, while the private key must be kept secret. In this implementation one key can be used to encrypt data, and the other key then must be used to decrypt it. When a server using PKI sends data to you, it will sign that data with its private key. You can then use the server’s public key to verify the signature and ensure the data has not been tampered with. The reverse can then happen too, when you send data to the server you can encrypt it with the server’s public key, and the server can then decrypt it with its private key. Which looks a bit like this:</p>

<p><img class="mermaid" src="https://mermaid.ink/svg/eyJjb2RlIjoiJSV7aW5pdDogeyd0aGVtZSc6ICdkYXJrJywgJ3RoZW1lVmFyaWFibGVzJzogeyAncHJpbWFyeUNvbG9yJzogJyNmZmNjMDAnLCAnZWRnZUxhYmVsQmFja2dyb3VuZCc6JyNmZmZmZmYnLCAndGVydGlhcnlDb2xvcic6ICcjZmZjYzAwJywgJ3NlY29uZGFyeUNvbG9yJzogJyNmZmNjMDAnLCAncHJpbWFyeVRleHRDb2xvcic6ICcjZmZmZmZmJywgJ3NlY29uZGFyeVRleHRDb2xvcic6ICcjZmZmZmZmJ319fSUlXG5zZXF1ZW5jZURpYWdyYW1cbnBhcnRpY2lwYW50IENsaWVudFxucGFydGljaXBhbnQgU2VydmVyXG4lJS1cbkNsaWVudC0-PlNlcnZlcjogUmVxdWVzdCBEYXRhXG5TZXJ2ZXItPj5TZXJ2ZXI6IFNpZ24gRGF0YSB3aXRoIFByaXZhdGUgS2V5XG5TZXJ2ZXItLT4-Q2xpZW50OiBTZW5kIFNpZ25lZCBEYXRhXG5DbGllbnQtPj5DbGllbnQ6IFZlcmlmeSBEYXRhIHdpdGggU2VydmVyJ3MgUHVibGljIEtleVxuJSUtXG5DbGllbnQtPj5TZXJ2ZXI6IFNlbmQgRW5jcnlwdGVkIERhdGEgKHVzaW5nIFNlcnZlcidzIFB1YmxpYyBLZXkpXG5TZXJ2ZXItPj5TZXJ2ZXI6IERlY3J5cHQgRGF0YSB3aXRoIFByaXZhdGUgS2V5XG5TZXJ2ZXItLT4-Q2xpZW50OiBBY2tub3dsZWRnZSBSZWNlaXB0IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifX0" /></p>

<p><strong>Spoiler alert:</strong> it’s actually more complex than this, but this is a good starting point to understand the basics of PKI.</p>

<p>For now, just know that as we work through understanding PKI there will be private/server keys and public keys in use as we discuss certificates.</p>

<p><a href="#top">Back to top</a></p>

<h3 id="so-what-is-a-certificate">So what is a certificate?</h3>

<p>Now that we understand what we mean by PKI, and we know that keys exist, let’s take a moment to understand what a certificate is.</p>

<p>A certificate is a digital document that contains information about the entity it represents. This information can include the entity’s name, the entity’s public key, validity period for this certificate, and the certificate’s issuer. The certificate is signed by the issuer, a CA, to ensure that the information contained within the certificate is accurate and has not been tampered with.</p>

<p>So, any server that you connect to securely, like you bank for example, will present you (or more accurately your computer) with a certificate. You and your system must then decide if you trust that certificate. If you do, then you can use the public key contained within the certificate to encrypt data that only the server can decrypt. The server can then use its private key to sign data that you can verify with the public key contained within the certificate. Thus we have a mechanism for ensuring that communication between your computer and the server is secure and that the data has not been tampered with.</p>

<p>Here is the certificate on this website:</p>

<p><img src="../../assets/images/blog/gww-cert.webp" alt="Certificate on this website" title="The certificate on www.grahamwatts.co.uk" /></p>

<p><a href="#top">Back to top</a></p>

<h2 id="hierarchies">Hierarchies</h2>

<p>Hierarchies are also referred to as certificate chains, and they are a critical part of PKI in practice.</p>

<p>We’ve touched on the fact that we have these things called Certificate Authorities, normally abbreviated to CAs. The job of a CA is to issue certificates. But, as we’ve also touched on, we need to protect the Root CA as it’s such a prize target for attackers. If you can compromise the Root CA, then you can issue certificates for anything you like. This is a problem because of <a href="#trust">Trust</a>, which we’ll get to in a moment. For now, let’s just consider what we might do to protect the Root CA. The obvious approach is to turn it off, a system that is turned off, or disconnected from the network, is much harder to attack. But, we still need to issue certificates to servers and services, so let’s create ourselves another CA and sign it’s certificate with the Root CA.</p>

<p>What’s happening here is that we’re now saying, “if you trust our root CA, then you can also now trust this intermediate CA”. Now, of course, this intermediate CA is also a target for attackers, but now, if this one is compromised we can <a href="#revocation">revoke</a> its certificate, and issue a new one from the root CA. As long as the root CA is secure, we can keep doing this and say “if you trust the root CA, then you can trust this new intermediate CA”.</p>

<p><img class="mermaid" src="https://mermaid.ink/svg/eyJjb2RlIjoiZ3JhcGggVERcblJvb3RbXCJSb290IENlcnRpZmljYXRlIEF1dGhvcml0eVwiXVxuU3Vib3JkaW5hdGVbXCJTdWJvcmRpbmF0ZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHlcIl1cbkVuZDFbXCJDbGllbnQgQ2VydGlmaWNhdGVzXCJdXG5FbmQyW1wiQ2xpZW50IENlcnRpZmljYXRlc1wiXVxuJSUtXG5Sb290IC0tLSBTdWJvcmRpbmF0ZVxuU3Vib3JkaW5hdGUgLS0tIEVuZDFcblN1Ym9yZGluYXRlIC0tLSBFbmQyXG4lJS1cbmNsYXNzRGVmIHJvb3QgZmlsbDojZmY5OTY2LHN0cm9rZTojZmZmZmZmLHN0cm9rZS13aWR0aDo0cHg7XG5jbGFzc0RlZiBzdWJvcmRpbmF0ZSBmaWxsOiM2NmNjZmYsc3Ryb2tlOiNmZmZmZmYsc3Ryb2tlLXdpZHRoOjRweDtcbmNsYXNzRGVmIGVuZEVudGl0eSBmaWxsOiNmZmZmZmYsc3Ryb2tlOiNmZmZmZmYsc3Ryb2tlLXdpZHRoOjRweCxjb2xvcjojMDAwMDAwO1xuJSUtXG5jbGFzcyBSb290IHJvb3Q7XG5jbGFzcyBTdWJvcmRpbmF0ZSBzdWJvcmRpbmF0ZTtcbmNsYXNzIEVuZDEsRW5kMiBlbmRFbnRpdHk7XG4lJS1cbmxpbmtTdHlsZSBkZWZhdWx0IHN0cm9rZTojZmZmZmZmLHN0cm9rZS13aWR0aDoycHg7IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifX0" /></p>

<p><a href="#top">Back to top</a></p>

<h3 id="subordinate-cas-intermediate-cas-policy-cas-and-issuing-cas">Subordinate CAs, Intermediate CAs, Policy CAs, and Issuing CAs</h3>

<p>So, Graham, you keep using these terms like “Subordinate CAs”, “Intermediate CAs”, “Policy CAs”, and “Issuing CAs”, I hear you say, “what do they all mean?”.</p>

<p>Well, let’s break it down.</p>

<p><a href="#top">Back to top</a></p>

<h4 id="root-cas">Root CAs</h4>

<p>This is where it all begins. The Root CA is the top of the hierarchy, the most crucial role in the hierarchy. The Root is the anchor of <a href="#trust">trust</a> for the entire PKI below it. If you trust the Root CA, then you can trust all the certificates issued by the Root CA, and all the certificates issued by the CAs below it. This all makes the Root CA the most critical part of the PKI, and the most likely target for attackers. To combat this, the Root CA is typically turned off, or at least disconnected from the network, and only brought online when it’s time to issue a new certificate to the next tier of subordinate CA(s).</p>

<p><a href="#top">Back to top</a></p>

<h4 id="subordinate-cas">Subordinate CAs</h4>

<p>A Subordinate CA is simply a CA that is below another CA in the hierarchy. We can see this is the diagram above, where the Subordinate CA is below the Root CA. That’s it, nothing more, a subordinate CA is just a CA that is below another CA in the hierarchy.</p>

<p><a href="#top">Back to top</a></p>

<h4 id="intermediate-cas">Intermediate CAs</h4>

<p>An Intermediate CA is a CA that is below another CA in the hierarchy, but it’s not the last CA in the chain. So it is a subordinate CA, but it’s not the last subordinate CA in the chain. Let’s revisit the diagram from the start of this article:</p>

<p><img class="mermaid" src="https://mermaid.ink/svg/eyJjb2RlIjoiZ3JhcGggVERcblJvb3RbXCJSb290IENlcnRpZmljYXRlIEF1dGhvcml0eVwiXVxuUG9saWN5W1wiUG9saWN5IENlcnRpZmljYXRlIEF1dGhvcml0eVwiXVxuSXNzdWUxW1wiSXNzdWluZzxici8-Q2VydGlmaWNhdGUgQXV0aG9yaXR5XCJdXG5Jc3N1ZTJbXCJJc3N1aW5nPGJyLz5DZXJ0aWZpY2F0ZSBBdXRob3JpdHlcIl1cbkVuZDFbXCJDbGllbnQgQ2VydGlmaWNhdGVzXCJdXG5FbmQyW1wiQ2xpZW50IENlcnRpZmljYXRlc1wiXVxuRW5kM1tcIkNsaWVudCBDZXJ0aWZpY2F0ZXNcIl1cbkVuZDRbXCJDbGllbnQgQ2VydGlmaWNhdGVzXCJdXG4lJS1cblJvb3QgLS0tIFBvbGljeVxuUG9saWN5IC0tLSBJc3N1ZTFcblBvbGljeSAtLS0gSXNzdWUyXG5Jc3N1ZTEgLS0tIEVuZDFcbklzc3VlMSAtLS0gRW5kMlxuSXNzdWUyIC0tLSBFbmQzXG5Jc3N1ZTIgLS0tIEVuZDRcbiUlLVxuY2xhc3NEZWYgcm9vdCBmaWxsOiNmZjk5NjYsc3Ryb2tlOiNmZmZmZmYsc3Ryb2tlLXdpZHRoOjRweDtcbmNsYXNzRGVmIHBvbGljeSBmaWxsOiM2NmNjZmYsc3Ryb2tlOiNmZmZmZmYsc3Ryb2tlLXdpZHRoOjRweDtcbmNsYXNzRGVmIGlzc3VpbmcgZmlsbDojOTBFRTkwLHN0cm9rZTojZmZmZmZmLHN0cm9rZS13aWR0aDo0cHg7XG5jbGFzc0RlZiBlbmRFbnRpdHkgZmlsbDojZmZmZmZmLHN0cm9rZTojZmZmZmZmLHN0cm9rZS13aWR0aDo0cHgsY29sb3I6IzAwMDAwMDtcbiUlLVxuY2xhc3MgUm9vdCByb290O1xuY2xhc3MgUG9saWN5IHBvbGljeTtcbmNsYXNzIElzc3VlMSxJc3N1ZTIgaXNzdWluZztcbmNsYXNzIEVuZDEsRW5kMixFbmQzLEVuZDQgZW5kRW50aXR5O1xuJSUtXG5saW5rU3R5bGUgZGVmYXVsdCBzdHJva2U6I2ZmZmZmZixzdHJva2Utd2lkdGg6MnB4OyIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In19" /></p>

<p>In this diagram, the <a href="#policy-cas">Policy CA</a> is an Intermediate CA. It’s <a href="#subordinate-cas">subordinate</a> to the Root CA, but it’s not the last CA in the chain. The two Issuing CAs are <a href="#subordinate-cas">subordinate CAs</a> to the Policy CA.</p>

<p><a href="#top">Back to top</a></p>

<h4 id="policy-cas">Policy CAs</h4>

<p>Now let’s get into the practicalities of deploying CAs and building out a PKI. A common pattern for deploying PKI is to separate out the roles of the CAs. The Root CA is the most secure, and the most critical, so we want to protect it as much as possible. The Root CA is typically turned off, or at least disconnected from the network, and only brought online when it’s time to issue a new certificate to the next tier of subordinate CA(s). The next tier of CAs are most commonly referred to as Policy CAs. The Policy CAs are responsible for defining the policy for the PKI, such as the validity period for certificates, the key length, and the algorithms used. The Policy CAs are also responsible for issuing certificates to the next tier of CAs, typically the Issuing CAs.</p>

<p><a href="#top">Back to top</a></p>

<h4 id="issuing-cas">Issuing CAs</h4>

<p>Now, we get to the day to day, core functionality of CAs. The Issuing CAs are responsible for issuing certificates to end entities, such as servers, services, and users. The Issuing CAs are the most commonly used CAs in the PKI. They are also the most likely to be compromised, because they are the most exposed to clients and the network. By having the <a href="#policy-cas">Policy CAs</a> and a <a href="#root-cas">Root CA</a> above the Issuing CAs, we can protect the validity of the chain, the hierarchy, by <a href="#revocation">revoking</a> the certificate of an Issuing CA if it is compromised, and issuing a new certificate from the Policy CA.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="trust">Trust</h2>

<p>Let’s talk about trust.</p>

<p>Trust is a critical component of PKI. If you don’t trust the CA that issued the certificate, then you can’t trust the certificate presented to you by a given server or service. That being said, we actually don’t need to know about, or trust every CA, and server certificate. The whole idea of PKI is that we can, in theory at least, simply trust a small number of <a href="#root-cas">Root CAs</a> and that will then allow us to trust all the CAs and certificates issued below then, in their chain of trust.</p>

<p>Restated simply; if you trust the Root CA, then you can trust any certificate which includes the Root CA at the top of the chain of trust.</p>

<p>Let’s take a deeper look at the certificate on this website:</p>

<p><img src="../../assets/images/blog/gww-cert-chain.webp" alt="Certificate chain for the certificate on this website" title="The certificate chain for www.grahamwatts.co.uk" /></p>

<p>Here we are looking at the certificate chain for this website. The certificate chain is the chain of certificates that lead back to the Root CA. In this case, the Root CA is <a href="https://www.digicert.com/digicert-root-certificates.htm"><code class="language-plaintext highlighter-rouge">DigiCert Global Root CA</code></a>. DigiCert are actually, in this case, only using a single <a href="#subordinate-cas">subordinate CA</a> to issue certificates. So we have a subordinate that is an <a href="#issuing-cas">Issuing CA</a> in this case. The certificate for this website is issued by the <code class="language-plaintext highlighter-rouge">GeoTrust Global TLS RSA4096 SHA256 2022 CA1</code>. So our chain looks like this:</p>

<p><img class="mermaid" src="https://mermaid.ink/svg/eyJjb2RlIjoiZ3JhcGggVERcblJvb3RbXCJEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQSAmbmJzcDsgJm5ic3A7XCJdXG5Jc3N1ZVtcIkdlb1RydXN0IEdsb2JhbCBUTFMgUlNBNDA5NiBTSEEyNTYgMjAyMiBDQTEgJm5ic3A7ICZuYnNwO1wiXVxuRW5kW1wiPGRpdj53d3cuZ3JhaGFtd2F0dHMuY28udWs8L2Rpdj5cIl1cbiUlLVxuUm9vdCAtLS0gSXNzdWVcbklzc3VlIC0tLSBFbmRcbiUlLVxuY2xhc3NEZWYgcm9vdCBmaWxsOiNmZjk5NjYsc3Ryb2tlOiNmZmZmZmYsc3Ryb2tlLXdpZHRoOjRweCxwYWRkaW5nOjEwcHg7XG5jbGFzc0RlZiBpc3N1aW5nIGZpbGw6IzkwRUU5MCxzdHJva2U6I2ZmZmZmZixzdHJva2Utd2lkdGg6NHB4LHBhZGRpbmc6MTBweDtcbmNsYXNzRGVmIGVuZEVudGl0eSBmaWxsOiNmZmZmZmYsc3Ryb2tlOiNmZmZmZmYsc3Ryb2tlLXdpZHRoOjRweCxjb2xvcjojMDAwMDAwLHBhZGRpbmc6MTBweDtcbiUlLVxuY2xhc3MgUm9vdCByb290O1xuY2xhc3MgSXNzdWUgaXNzdWluZztcbmNsYXNzIEVuZCBlbmRFbnRpdHk7XG4lJS1cbmxpbmtTdHlsZSBkZWZhdWx0IHN0cm9rZTojZmZmZmZmLHN0cm9rZS13aWR0aDoycHg7IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifX0" /></p>

<p>Now, depending on your system you may, or may not, have the <code class="language-plaintext highlighter-rouge">GeoTrust Global TLS RSA4096 SHA256 2022 CA1</code> certificate in your trust store. But, as long as you do have the <code class="language-plaintext highlighter-rouge">DigiCert Global Root CA</code> certificate in your trust store, then you can trust the certificate for this website.</p>

<p>We can actually see that on my system, right now. Don’t worry so much about the command that I am running, just note that whilst I do have the <code class="language-plaintext highlighter-rouge">Digicert Global Root CA</code> certificate in my trust store, I don’t have the <code class="language-plaintext highlighter-rouge">GeoTrust Global TLS RSA4096 SHA256 2022 CA1</code> there.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>trust list <span class="nt">--filter</span><span class="o">=</span>ca-anchors | <span class="nb">grep</span> <span class="nt">-iE</span> <span class="s1">'DigiCert Global Root CA|GeoTrust'</span>
    label: DigiCert Global Root CA
    label: GeoTrust Global CA
    label: GeoTrust Global CA 2
    label: GeoTrust Primary Certification Authority
    label: GeoTrust Primary Certification Authority - G2
    label: GeoTrust Primary Certification Authority - G3
    label: GeoTrust Universal CA
    label: GeoTrust Universal CA 2
</code></pre></div></div>

<p>Yet, my browser is happy to show me this website and report the connection as “secure”.</p>

<p><img src="../../assets/images/blog/secure-gww.webp" alt="Secure connection to this website" title="A secure connection to www.grahamwatts.co.uk" /></p>

<p>This is the trust hierarchy in action. My system trusts the root CA, and so therefore it can trust the certificate for this website, even though it doesn’t have the intermediate CA certificate in its trust store.</p>

<p><a href="#top">Back to top</a></p>

<h3 id="importing-certificate-chains">Importing certificate chains</h3>

<p>Now, if you’ve ever worked with PKI or signed your own web server then you’re probably out there saying “but my certificate issuer gave me a chain file and the instructions were clear that I had to install it”, or perhaps you run your own PKI and always make sure that you import the full chain of CAs. Whilst yes, just trusting the root CA should be enough, it can require additional processing to go out and fetch and verify each certificate in the chain. So, it’s often easier, and better practice, where possible, to import the full chain of CA certificates into your trust store. Service verification and page load will be faster, and you’ll have more confidence that the certificate is valid.</p>

<p>Another reason is that some systems, like browsers, will only trust certificates that are signed by a CA that they directly trust. So, if you’re running a web server, and you want to make sure that all browsers trust your certificate, then you’ll need to make sure that you import the full chain of CA certificates into your trust store.</p>

<p>Finally there is the issue of <a href="#revocation">revocation</a>. If you have the full chain of CA certificates in your trust store, then you can be sure that you can trust the revocation status of the certificate. If you only have the root CA certificate in your trust store, then you may not be able to trust the revocation status of the certificate, and may not have sufficient information to be able to query the revocation status of the certificate.</p>

<p><a href="#top">Back to top</a></p>

<h3 id="trust-errors-for-self-signed-certificates">Trust errors for self-signed certificates</h3>

<p>We’ve not talked about self-signed certificates yet, but let’s touch on them briefly here.</p>

<p>It is possible to generate your own certificate without a CA involved. Infact, many services do this as a default to get their system up and running, and then prompt you to install a signed certificate later, as part of the setup.</p>

<p>When you connect to a service that is using a self-signed certificate, your system will typically report a trust error. This is because your system doesn’t trust the certificate. This is because it’s not signed by a any CA that it is aware of. You can still connect to the service, but you’ll need to manually approve the connection acknowledging this unrecognised certificate, or you can manually import the certificate into the trust store. But, you should be aware that the certificate is not signed by a CA that your system trusts, and so you should be cautious about trusting the certificate.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="revocation">Revocation</h2>

<p>The last thing that we will discuss today is the concept of revocation, seeing as I’ve mentioned it a few times already.</p>

<p>Revocation is the process of invalidating a certificate before it’s expiry date. This can happen for a number of reasons, such as the issuing CA, or the certificate itself being compromised, the certificate being issued in error, or the certificate being revoked by the CA for some other reason. When a certificate is revoked, the CA will issue a new certificate to replace the revoked certificate. The CA will also publish the revocation status of the certificate in a <a href="https://en.wikipedia.org/wiki/Certificate_revocation_list">Certificate Revocation List (CRL)</a> or an <a href="https://en.wikipedia.org/wiki/Online_Certificate_Status_Protocol">Online Certificate Status Protocol (OCSP)</a> response. These mechanisms allow clients to check the revocation status of a certificate before trusting it.</p>

<p>If your client identifies that a certificate has been revoked, then it should not trust the certificate (or the CA if the CA itself is revoked). In this case it can report a trust error, or simply refuse to connect to the service.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="conclusion">Conclusion</h2>

<p>So, there you have it, a high level introduction to Public Key Infrastructure (PKI) and certificates. We’ve covered the basics of PKI, the hierarchy of CAs, trust, and revocation. We’ve also touched on the importance of trust, and the implications of not trusting a certificate. I hope that this article has helped to demystify PKI and certificates for you, and that you now feel more confident in understanding the concepts and processes involved in PKI.</p>

<p>I’ll be writing more about PKI in the future, so if you have any questions or topics that you’d like me to cover, then please let me know.</p>

<p>If this article helped inspire you please consider sharing this article with your friends and colleagues, or let me know via <a href="https://www.linkedin.com/in/wattsgraham/">LinkedIn</a> or <a href="https://twitter.com/Ginger_Graham">X / Twitter</a>.  If you have any ideas for further content you might like to see please let me know too.</p>

<p><a href="#top">Back to top</a></p>]]></content><author><name>Graham Watts</name><email>mail@grahamwatts.co.uk</email></author><category term="blog" /><category term="technical" /><category term="knowledge" /><category term="security" /><category term="technical" /><category term="security" /><summary type="html"><![CDATA[In this article I want to explore, and demystify, the subject of Public Key Infrastructure (PKI) and certificates.]]></summary></entry><entry><title type="html">Managing Secrets In Windows</title><link href="https://www.grahamwatts.co.uk/windows-secrets/" rel="alternate" type="text/html" title="Managing Secrets In Windows" /><published>2024-10-17T00:00:00+01:00</published><updated>2025-11-30T00:00:00+00:00</updated><id>https://www.grahamwatts.co.uk/windows-secrets</id><content type="html" xml:base="https://www.grahamwatts.co.uk/windows-secrets/"><![CDATA[<p>A little while ago I wrote an article on <a href="/gnome-secrets">Managing Secrets In Linux</a> which covered some of the tools available to manage secrets on a Linux system.  I wanted to follow that up by exploring similar techniques for managing secrets on Windows. In researching this piece I found that in all of the articles I found some of the guidance, including a key detail they all refer to, is outdated. So this is my spin on the subject, where I’ll address the issue with the other articles I have found, acknowledge their great work and walk you step by step through managing secrets on Windows.</p>

<h2 id="introducing-credential-manager">Introducing Credential Manager</h2>

<p>Starting from the top; Windows has a built-in tool called Credential Manager that was first introduced with Windows 7. Credential Manager is a tool that stores your credentials, such as usernames and passwords, in a secure location on your computer. You can use Credential Manager to store credentials for websites, network locations, and applications. Credential Manager can be a useful tool for managing your passwords and other sensitive information.</p>

<p><a href="https://support.microsoft.com/en-gb/windows/accessing-credential-manager-1b5c916a-6a16-889f-8581-fc16e8165ac0">Microsoft’s own documentation on Credential Manager</a> that you can find online is surprisingly limited! Fortunately, however, a wide range of sources such as <a href="https://woshub.com/saved-passwords-windows-credential-manager/">Windows OS Hub</a> do a much better job of discussing the tool (<strong>caution:</strong> this article is victim of the <a href="#the-powershell-credential-manager-gotcha">“gotcha”</a> that I’ll discuss below).</p>

<p>You can find Credential Manager by searching for it in the Start menu or by going to Control Panel &gt; User Accounts &gt; Credential Manager.</p>

<p><img src="../../assets/images/blog/WindowsSecrets/EmptyCredentialManager.webp" alt="Credential Manager" title="Windows Credential Manager" /></p>

<p><a href="#top">Back to top</a></p>

<h3 id="the-different-credential-types">The Different Credential Types</h3>

<p>Credential Manager can store three different types of credentials:</p>

<ol>
  <li><strong>Windows Credentials</strong>: These are credentials that are used to log in to services that support Windows authentication such as Remote Desktop connections, network shares, and websites that use Windows authentication.</li>
  <li><strong>Certificate-Based Credentials</strong>: These are credentials that are used to authenticate with websites and services that require a certificate for authentication. Certificates are sourced from the Personal directory of the Windows Certificate Store.</li>
  <li><strong>Generic Credentials</strong>: These are credentials that are used to log in to websites and services that do not support Windows authentication or certificate-based authentication. Generic credentials can be used to store any type of credential, such as usernames and passwords for websites, network shares, and applications.</li>
</ol>

<p>Mostly in this article I’ll be working with Generic credentials but the same principles apply to the other types as well.</p>

<h2 id="working-with-credentials-in-the-gui">Working With Credentials In The GUI</h2>

<p>Before we get to the command line magic let’s quickly talk about using the GUI; this is Windows after all and if it didn’t have the “windows” then it might as well be Linux, right(?)!  The Credential Manager GUI is pretty straightforward to use.  You can add, edit, and remove credentials from the Credential Manager as follows:</p>

<h3 id="adding-credentials-in-the-gui">Adding Credentials In The GUI</h3>

<ol>
  <li>Open Credential Manager by searching for it in the Start menu or by going to Control Panel &gt; User Accounts &gt; Credential Manager.</li>
  <li>Click on “Add a Windows credential” or “Add a generic credential” depending on the type of credential you want to add. We’ll be adding a generic credential in this example.</li>
  <li>Enter the name of the server or website you want to store the credentials for in the “Internet or network address” field. This field can also be used as a friendly name for the credential if you’re not going to be using it for a network address. We’ll use it for a friendly name in this example.</li>
  <li>Enter your username and password in the “User name” and “Password” fields respectively.</li>
</ol>

<p><img src="../../assets/images/blog/WindowsSecrets/GUI-Manual-Cred-Add.webp" alt="Adding a generic credential" title="Adding a generic credential in the Credential Manager GUI" /></p>

<h3 id="editing-credentials-in-the-gui">Editing Credentials In The GUI</h3>

<ol>
  <li>Open Credential Manager by searching for it in the Start menu or by going to Control Panel &gt; User Accounts &gt; Credential Manager.</li>
  <li>Click on the credential you want to edit and expand it by clicking on the down arrow next to it.</li>
  <li>Click on “Edit” to edit the credential.</li>
  <li>Make your changes and click “Save”.</li>
</ol>

<p><img src="../../assets/images/blog/WindowsSecrets/GUI-Edit-Cred.webp" alt="Editing a generic credential" title="Editing a generic credential in the Credential Manager GUI" /></p>

<h3 id="removing-credentials-in-the-gui">Removing Credentials In The GUI</h3>

<ol>
  <li>Open Credential Manager by searching for it in the Start menu or by going to Control Panel &gt; User Accounts &gt; Credential Manager.</li>
  <li>Click on the credential you want to remove and expand it by clicking on the down arrow next to it.</li>
  <li>Click on “Remove” to remove the credential.</li>
</ol>

<p><a href="#top">Back to top</a></p>

<h2 id="working-with-credentials-on-the-command-line">Working With Credentials On The Command Line</h2>

<p>OK! On with the “fun stuff”, let’s get to the command line! There are tools of varying capability across both PowerShell and the Command Prompt (<code class="language-plaintext highlighter-rouge">cmd</code>), and we’ll have a look at both. Let’s start with PowerShell as the more modern and capable of the two.</p>

<h3 id="powershell-credential-manager-tools">PowerShell Credential Manager Tools</h3>

<p>PowerShell, both Windows Powershell (&lt;=v5.x) and modern, cross-platform PowerShell (6 and 7 at the time of writing) can be extended with an installable module called <code class="language-plaintext highlighter-rouge">CredentialManager</code> that provides a set of cmdlets for working with the Credential Manager. But… we need to talk about the <a href="#the-powershell-credential-manager-gotcha">gotcha</a> first.</p>

<h4 id="the-powershell-credential-manager-gotcha">The PowerShell Credential Manager Gotcha</h4>

<p>Articles like the one we saw earlier from <a href="https://woshub.com/saved-passwords-windows-credential-manager/#h2_3">Windows OS Hub</a> or this one from <a href="https://www.delftstack.com/howto/powershell/use-credential-manager-in-powershell/">DelftStack</a> all refer to a module called <code class="language-plaintext highlighter-rouge">CredentialManager</code> that you can install from the PowerShell Gallery using the cmdlet <code class="language-plaintext highlighter-rouge">Install-Module -Name CredentialManager</code>. The problem is that this module was archived back in September 2021 and is no longer maintained (see the <a href="https://github.com/davotronic5000/PowerShell_Credential_Manager">GitHub repository</a>). While it worked for a while more recent releases of PowerShell, particular in the cross-platform PowerShell 7 family seem to have broken compatibility with the module.</p>

<p>Where this will become evident is when you try to <a href="#adding-credentials-in-powershell">add a credential</a> with the <code class="language-plaintext highlighter-rouge">New-StoredCredential</code> cmdlet. You’ll get an error message like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>New-StoredCredential: Argument 'New-StoredCredential' is not recognized as a cmdlet: Could not load type 'System.Web.Security.Membership' from assembly 'System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
</code></pre></div></div>

<p><img src="../../assets/images/blog/WindowsSecrets/OldModule-NewPS.webp" alt="Error message when adding a credential in PowerShell" title="Error message when adding a credential in PowerShell using the old CredentialManager module" /></p>

<p>Fortunately the project was forked and an updated version released <a href="https://www.powershellgallery.com/packages/TUN.CredentialManager/3.0.2">here</a> and its <a href="https://github.com/echalone/PowerShell_Credential_Manager">GitHub here</a>. This means that with a minor adjustment to the installation command you can still use the module’s cmdlets. The new command is <code class="language-plaintext highlighter-rouge">Install-Module -Name TUN.CredentialManager</code>.</p>

<p>After installing this version all of the cmdlets are the same as the original module, so you can follow the guidance in the articles I linked to earlier with the updated module. Of course, I’ll save you going to those articles and provide the guidance here…</p>

<p><a href="#top">Back to top</a></p>

<h4 id="installing-the-powershell-credential-manager-module">Installing The PowerShell Credential Manager Module</h4>

<p>To install the updated version of the <code class="language-plaintext highlighter-rouge">CredentialManager</code> module, open PowerShell and run the following command:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Install-Module</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">TUN.CredentialManager</span><span class="w">
</span></code></pre></div></div>

<p><img src="../../assets/images/blog/WindowsSecrets/Install-Module.webp" alt="Installing the CredentialManager module" title="Installing the CredentialManager module in PowerShell" /></p>

<p>In the example above I added a couple of extra switches to the <code class="language-plaintext highlighter-rouge">Install-Module</code> cmdlet. The <code class="language-plaintext highlighter-rouge">-Force</code> switch is used to force the installation of the module without prompting for confirmation. The <code class="language-plaintext highlighter-rouge">-AllowClobber</code> switch is used to allow the module to overwrite any existing modules with the same name. This is useful if you have an older version of the module installed and you want to update it to the latest version, and <code class="language-plaintext highlighter-rouge">-Verbose</code> is used to provide more detailed output about the installation process.</p>

<p><a href="#top">Back to top</a></p>

<h4 id="importing-the-powershell-credential-manager-module">Importing The PowerShell Credential Manager Module</h4>

<p>Once the module is installed you can import it into your PowerShell session with the following command:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Import-Module</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">TUN.CredentialManager</span><span class="w">
</span></code></pre></div></div>

<p>Also note that immediately after install some versions of PowerShell have had an issue where the <a href="https://github.com/PowerShell/PowerShell/issues/11533">module is not imported automatically</a>. If you find that the module is not available after installation you can import it manually with the command above.</p>

<p><a href="#top">Back to top</a></p>

<h4 id="adding-credentials-in-powershell">Adding Credentials In PowerShell</h4>

<p>Let’s start with a simple example to get started; side note, please <strong>do not</strong> use this example outside of practice. I hope it goes without saying but entering your password in plain text in a script or on the command line is a <strong>bad idea</strong>.  That said, here’s how you can add a credential in PowerShell:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">New-StoredCredential</span><span class="w"> </span><span class="nt">-Target</span><span class="w"> </span><span class="s1">'MyCredential'</span><span class="w"> </span><span class="nt">-UserName</span><span class="w"> </span><span class="s1">'MyUsername'</span><span class="w"> </span><span class="nt">-Password</span><span class="w"> </span><span class="s1">'MyPassword'</span><span class="w">
</span></code></pre></div></div>

<p><img src="../../assets/images/blog/WindowsSecrets/Import-And-NewCred-01.webp" alt="Adding a credential in PowerShell" title="Adding a credential in PowerShell" /></p>

<p>Once we’ve done this we can check in Credential Manager to see that the credential has been added:</p>

<p><img src="../../assets/images/blog/WindowsSecrets/NewCred01-And-CredMan.webp" alt="Checking the credential in Credential Manager" title="Checking the credential in Credential Manager" /></p>

<p>But remember, this is a bad idea!  So let’s look at a better way to do this…</p>

<p>We can use the <code class="language-plaintext highlighter-rouge">Get-Credential</code> cmdlet to prompt for a username and password and store the credential in a variable.  Here’s an example:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-Credential</span><span class="w"> </span><span class="nt">-Message</span><span class="w"> </span><span class="s1">'Enter your credentials'</span><span class="w"> </span><span class="nt">-UserName</span><span class="w"> </span><span class="s1">'MyUsername'</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">New-StoredCredential</span><span class="w"> </span><span class="nt">-Target</span><span class="w"> </span><span class="s1">'MyCredential
</span></code></pre></div></div>

<p>Depending on the version of PowerShell you are using it may either prompt you for the password in the CLI or it may pop a dialog box up for you to enter the password.  Either way, the password is not stored in plain text in the script.</p>

<p><img src="../../assets/images/blog/WindowsSecrets/Get-Cred-WithPopUp.webp" alt="Adding a credential in PowerShell with Get-Credential" title="Adding a credential in PowerShell with Get-Credential" /></p>

<p>As we can see this stored in the response to the cmdlet.</p>

<p><img src="../../assets/images/blog/WindowsSecrets/Demo02-Saved.webp" alt="Credential added with Get-Credential" title="Credential added with Get-Credential" /></p>

<p>As you can see the password is not displayed in plain text whilst the credential is being added.</p>

<p>We can see both credentials in Credential Manager regardless of how they were added.</p>

<p><img src="../../assets/images/blog/WindowsSecrets/CredManager-Demo01-And-02.webp" alt="Checking the credentials in Credential Manager" title="Checking the credentials in Credential Manager" /></p>

<p>If you’re sharp-eyed you may have noticed a difference in the way the password is stored between the two methods.  In the first, where we used the <code class="language-plaintext highlighter-rouge">-Password</code> switch, the password is stored in plain text.  In the second, where we used <code class="language-plaintext highlighter-rouge">Get-Credential</code>, the password is stored as a <code class="language-plaintext highlighter-rouge">SecurePassword</code> object. When we <a href="#retrieving-credentials-in-powershell">retrieve the credential</a> we’ll see that this will make a difference in what is displayed.</p>

<p>If we wanted to store the password securely without needing to prompt the user, for example if we are creating the password programmatically, we can use the <code class="language-plaintext highlighter-rouge">ConvertTo-SecureString</code> cmdlet to convert the password to a secure string. Here’s an example:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$Password</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'MyPassword'</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ConvertTo-SecureString</span><span class="w"> </span><span class="nt">-AsPlainText</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="c"># Obviously we're passing the value in plain text here but you could use a variable or other method to pass the password</span><span class="w">
</span><span class="n">New-StoredCredential</span><span class="w"> </span><span class="nt">-Target</span><span class="w"> </span><span class="s1">'MyCredential'</span><span class="w"> </span><span class="nt">-UserName</span><span class="w"> </span><span class="s1">'MyUsername'</span><span class="w"> </span><span class="nt">-SecurePassword</span><span class="w"> </span><span class="nv">$Password</span><span class="w"> </span><span class="nt">-Persist</span><span class="w"> </span><span class="nx">LocalMachine</span><span class="w">
</span></code></pre></div></div>

<p>Or we could prompt the user another way using the <code class="language-plaintext highlighter-rouge">Read-Host</code> cmdlet:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$Password</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Read-Host</span><span class="w"> </span><span class="nt">-Prompt</span><span class="w"> </span><span class="s1">'Enter your password'</span><span class="w"> </span><span class="nt">-AsSecureString</span><span class="w">
</span><span class="n">New-StoredCredential</span><span class="w"> </span><span class="nt">-Target</span><span class="w"> </span><span class="s1">'MyCredential'</span><span class="w"> </span><span class="nt">-UserName</span><span class="w"> </span><span class="s1">'MyUsername'</span><span class="w"> </span><span class="nt">-SecurePassword</span><span class="w"> </span><span class="nv">$Password</span><span class="w"> </span><span class="nt">-Persist</span><span class="w"> </span><span class="nx">LocalMachine</span><span class="w">
</span></code></pre></div></div>

<p><a href="#top">Back to top</a></p>

<h5 id="credential-persistence">Credential Persistence</h5>

<p>One thing to note is that credentials added in this way can have different levels of persistence. The eagle-eyed amongst you may have spotted that I specified a <code class="language-plaintext highlighter-rouge">-Persist</code> switch in the second credential I added above (see the pictures). This switch can be used to specify the persistence of the credential. The options are:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Session</code>: The credential is stored for the duration of the current session. Logging out or rebooting will remove the credential.</li>
  <li><code class="language-plaintext highlighter-rouge">LocalMachine</code>: The credential is stored for the current user on the local machine. The credential will persist across sessions but will not be available to other users on the machine.</li>
  <li><code class="language-plaintext highlighter-rouge">Enterprise</code>: The credential available to all authenticated users on the domain.</li>
</ul>

<p>If you don’t specify a persistence level the default is <code class="language-plaintext highlighter-rouge">Session</code>.</p>

<p>Source: <a href="https://dev.to/issamboutissante/how-to-readwrite-from-credential-manager-in-net-8-1ag">dev.to</a></p>

<p><a href="#top">Back to top</a></p>

<h4 id="retrieving-credentials-in-powershell">Retrieving Credentials In PowerShell</h4>

<p>To retrieve a credential in PowerShell you can use the <code class="language-plaintext highlighter-rouge">Get-StoredCredential</code> cmdlet. Here’s an example:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-StoredCredential</span><span class="w"> </span><span class="nt">-Target</span><span class="w"> </span><span class="s1">'MyCredential
</span></code></pre></div></div>

<p><img src="../../assets/images/blog/WindowsSecrets/Get-StoredCred.webp" alt="Retrieving a credential in PowerShell" title="Retrieving a credential in PowerShell" /></p>

<p>However this does not show us much useful information.  If you want to see details including the password we can add the <code class="language-plaintext highlighter-rouge">-AsCredentialObject</code> switch:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-StoredCredential</span><span class="w"> </span><span class="nt">-Target</span><span class="w"> </span><span class="s1">'MyCredential -AsCredentialObject
</span></code></pre></div></div>

<p><img src="../../assets/images/blog/WindowsSecrets/Get-StoredCred-Detail.webp" alt="Retrieving a credential in PowerShell with the password" title="Retrieving a credential in PowerShell with the password" /></p>

<p>Also, note, that for credentials gathered using a method where the password has not been passed in plain text, such as the example above using <code class="language-plaintext highlighter-rouge">Get-Credential</code>, the password can still be retrieved using this method.</p>

<p><img src="../../assets/images/blog/WindowsSecrets/Demo02-Saved.webp" alt="Retrieving a credential in PowerShell even when the password was not passed in plain text" title="Retrieving a credential in PowerShell even when the password was not passed in plain text" /></p>

<p>So, if there’s a credential saved in Credential Manager that you need to find the password for, because you’ve forgotten or lost it, you can use this cmdlet to retrieve it.</p>

<p>In some instances you may wish to query the credentials to the console but not reveal the password. The <code class="language-plaintext highlighter-rouge">TUN.CredentialManager</code> module has an additional switch, <code class="language-plaintext highlighter-rouge">-ExcludeClearPassword</code> which will allow you to do this</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-StoredCredential</span><span class="w"> </span><span class="nt">-Target</span><span class="w"> </span><span class="s1">'MyCredential -ExcludeClearPassword -AsCredentialObject
</span></code></pre></div></div>

<p><img src="../../assets/images/blog/WindowsSecrets/ExcludePassword.webp" alt="Retrieving a credential in PowerShell without the password" title="Retrieving a credential in PowerShell without the password" /></p>

<p>More usefully though is going to be using the credential in a script.  Here’s an example of how you can use the credential in a script:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$Credential</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-StoredCredential</span><span class="w"> </span><span class="nt">-Target</span><span class="w"> </span><span class="s1">'MyCredential -AsCredentialObject
$Username = $Credential.UserName
$Password = $Credential.Password
Write-Output "Username: $Username"
Write-Output "Password: $Password"
</span></code></pre></div></div>

<p>Where the password is stored securely you may need to convert it back from a secure string to a plain text string.  Here’s an example of how you can do that:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$Credential</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-StoredCredential</span><span class="w"> </span><span class="nt">-Target</span><span class="w"> </span><span class="s1">'MyCredential
$Username = $Credential.UserName
$securePW = $Credential.Password
$pw = ConvertFrom-SecureString -SecureString $securePW -AsPlainText
Write-Output "Username: $Username"
Write-Output "Password: $pw"
</span></code></pre></div></div>

<p>Anyway, let’s look at a real use case for all of this. In the example below I have an API key for <a href="https://openweathermap.org/">OpenWeatherMap</a> stored in Credential Manager. I can retrieve this key and use it in a script to get the current weather for a location:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$owm</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-StoredCredential</span><span class="w"> </span><span class="nt">-Target</span><span class="w"> </span><span class="nx">openweathermap-api</span><span class="w"> </span><span class="nt">-AsCredentialObject</span><span class="w">
</span><span class="n">Invoke-RestMethod</span><span class="w"> </span><span class="nt">-Uri</span><span class="w"> </span><span class="s2">"https://api.openweathermap.org/data/2.5/weather?q=London&amp;appid=</span><span class="si">$(</span><span class="nv">$owm</span><span class="o">.</span><span class="nf">Password</span><span class="si">)</span><span class="s2">"</span><span class="w"> </span><span class="nt">-Method</span><span class="w"> </span><span class="nx">Get</span><span class="w">
</span></code></pre></div></div>

<p><img src="../../assets/images/blog/WindowsSecrets/OWM-API-Example.webp" alt="Using a credential in a script" title="Using a credential in a script to get the current weather from OpenWeatherMap" /></p>

<p>So now we have a method for securely storing and using credentials in PowerShell scripts.</p>

<p><a href="#top">Back to top</a></p>

<h4 id="removing-credentials-in-powershell">Removing Credentials In PowerShell</h4>

<p>To remove a credential in PowerShell you can use the <code class="language-plaintext highlighter-rouge">Remove-StoredCredential</code> cmdlet. Here’s an example:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Remove-StoredCredential</span><span class="w"> </span><span class="nt">-Target</span><span class="w"> </span><span class="s1">'MyCredential
</span></code></pre></div></div>

<p><img src="../../assets/images/blog/WindowsSecrets/Remove-Cred.webp" alt="Removing a credential in PowerShell" title="Removing a credential in PowerShell" /></p>

<p><a href="#top">Back to top</a></p>

<h3 id="command-prompt-credential-manager-tools">Command Prompt Credential Manager Tools</h3>

<p>The Command Prompt (<code class="language-plaintext highlighter-rouge">cmd</code>) doesn’t have the same level of capability as PowerShell but it does have a couple of commands that can be used to work with the Credential Manager. The <code class="language-plaintext highlighter-rouge">cmdkey</code> command can be used to add, list, and remove credentials from the Credential Manager.</p>

<p>Note this time we have a list option which actually isn’t available in the PowerShell cmdlets. Your mileage may vary but this could still make <code class="language-plaintext highlighter-rouge">cmdkey</code> a useful tool for you.</p>

<p>The major drawback to using <code class="language-plaintext highlighter-rouge">cmdkey</code> is that it doesn’t have the ability to recover the password for a credential.  This means that it is much more limited for scripting as we cannot lookup a password to use in a script.</p>

<h4 id="listing-credentials-in-command-prompt">Listing Credentials In Command Prompt</h4>

<p>To list the credentials in the Credential Manager using the Command Prompt you can use the <code class="language-plaintext highlighter-rouge">cmdkey</code> command with the <code class="language-plaintext highlighter-rouge">/list</code> switch. Here’s an example:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cmdkey /list
</code></pre></div></div>

<p><img src="../../assets/images/blog/WindowsSecrets/cmdkey-list.webp" alt="Listing credentials in Command Prompt" title="Listing credentials in Command Prompt" /></p>

<h4 id="adding-credentials-in-command-prompt">Adding Credentials In Command Prompt</h4>

<p>To add a credential in the Credential Manager using the Command Prompt you can use the <code class="language-plaintext highlighter-rouge">cmdkey</code> command with the <code class="language-plaintext highlighter-rouge">/add</code> or <code class="language-plaintext highlighter-rouge">/generic</code> switch. As with the other examples we’ll be adding a generic credential in this example. Here’s an example:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cmdkey /add:MyCredential /user:MyUsername /pass:MyPassword
</code></pre></div></div>

<p><img src="../../assets/images/blog/WindowsSecrets/cmdkey-add-generic.webp" alt="Adding a credential in Command Prompt" title="Adding a credential in Command Prompt" /></p>

<p>That’s about all there is to it.  As I mentioned earlier, the major drawback to using <code class="language-plaintext highlighter-rouge">cmdkey</code> is that it doesn’t have the ability to recover the password for a credential making it a much more limited tool.</p>

<h4 id="removing-credentials-in-command-prompt">Removing Credentials In Command Prompt</h4>

<p>To remove a credential in the Credential Manager using the Command Prompt you can use the <code class="language-plaintext highlighter-rouge">cmdkey</code> command with the <code class="language-plaintext highlighter-rouge">/delete</code> switch. Here’s an example:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cmdkey /delete:MyCredential
</code></pre></div></div>

<p><img src="../../assets/images/blog/WindowsSecrets/cmdkey-delete.webp" alt="Removing a credential in Command Prompt" title="Removing a credential in Command Prompt" /></p>

<p><a href="#top">Back to top</a></p>

<h2 id="update-looking-for-ways-to-work-with-secrets-on-macos">Update: Looking for ways to work with secrets on macOS?</h2>

<p>If you’re looking for ways to work with secrets on macOS, check out my post on <a href="/macos-secrets/">Managing Secrets in macOS</a>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>In this article, we’ve looked at how to manage secrets in Windows using the Credential Manager. We’ve seen how to add, edit, and remove credentials in the GUI, and how to do the same in the command line using PowerShell and the Command Prompt. We’ve also seen how to retrieve credentials and use them in scripts.  We’ve also seen how to use the <code class="language-plaintext highlighter-rouge">cmdkey</code> command in the Command Prompt to manage credentials.</p>

<p>Most importantly, for me at least, I’ve covered the issue with the outdated <code class="language-plaintext highlighter-rouge">CredentialManager</code> module for PowerShell and provided a way that we can continue to work with credentials using the newer <code class="language-plaintext highlighter-rouge">TUN.CredentialManager</code> module.</p>

<p>Hopefully what you can take away from this article is that there are a number of ways to manage secrets in Windows and that if you are working with secrets in scripts on Windows you now have the tools to do so securely in a way that is integrated with the Windows operating system.</p>

<p>If this article helped inspire you please consider sharing this article with your friends and colleagues, or let me know via <a href="https://www.linkedin.com/in/wattsgraham/">LinkedIn</a> or <a href="https://twitter.com/Ginger_Graham">X / Twitter</a>.  If you have any ideas for further content you might like to see please let me know too.</p>

<p><a href="#top">Back to top</a></p>]]></content><author><name>Graham Watts</name><email>mail@grahamwatts.co.uk</email></author><category term="blog" /><category term="technical" /><category term="windows" /><category term="security" /><summary type="html"><![CDATA[A look at some tools to manage secrets when working on Windows]]></summary></entry><entry><title type="html">Lock Files in bash</title><link href="https://www.grahamwatts.co.uk/blog/2024/10/09/lock-files-in-bash/" rel="alternate" type="text/html" title="Lock Files in bash" /><published>2024-10-09T00:00:00+01:00</published><updated>2024-10-09T00:00:00+01:00</updated><id>https://www.grahamwatts.co.uk/blog/2024/10/09/lock-files-in-bash</id><content type="html" xml:base="https://www.grahamwatts.co.uk/blog/2024/10/09/lock-files-in-bash/"><![CDATA[<p>I was working on a project recently where I needed to control the processing of bash scripts across multiple machines. It took a little research, and some trial and error and so I thought I would share my experiences and findings with you so you can implement this in your own projects.</p>

<p>Before we get into it, let’s talk about a recent ticket I’ve been working on as a case study. The project called for 3 virtual machines as part of an auto-scaling group (we’re working on AWS here, but the same logic would apply to any similar deployment). The machines are not formally clustered but need to operate in a coordinated manner. The machines were to be a yum repository mirror. What’s important here though is that we can a centralised data store, in this case using <a href="https://aws.amazon.com/efs/">EFS</a>, which all the machines can access, and they needed to sync a number of repositories both on startup and on a regular schedule. We wanted to avoid race conditions and multiple nodes duplicating work by trying to sync the same repos at the same time. So we needed a way to tell all the other nodes that one node was currently syncing a set of repos and to step over to another sync action or quit if there was nothing else to do. We combined the techniques discussed here with the use of a <a href="/blog/2024/10/09/systemd-unit-files-for-tasks/">SystemD Unit</a> to manage the execution of the script.</p>

<p>Enter lock files…</p>

<h2 id="what-is-a-lock-file">What is a lock file?</h2>

<p>A lock file is, itself, nothing special at all. It’s just a file that exists on the filesystem. The magic comes from the fact that it’s used to signal to other processes that a particular process is currently running. The <a href="https://dictionary.cambridge.org/dictionary/english/tldr">TLDR</a> if you are in a hurry is you check if the file exists, if it does then that signals that another process is running and you should stop trying to process the same thing. If it doesn’t exist then you create it and carry on with the rest of the script logic.</p>

<h2 id="implementing-lock-files">Implementing lock files</h2>

<p>OK, so that seems easy enough, so how do we implement this in bash? Well, it’s actually quite simple. Here’s an example for you:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="nv">LOCKFILE</span><span class="o">=</span>/tmp/mylockfile <span class="c"># Set the lock file location to whatever path you need</span>

<span class="k">if</span> <span class="o">[</span> <span class="nt">-f</span> <span class="nv">$LOCKFILE</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"Lock file exists, exiting"</span>
    <span class="nb">exit </span>1
<span class="k">fi

</span><span class="nb">touch</span> <span class="nv">$LOCKFILE</span>

<span class="c"># Do some work here</span>

<span class="nb">rm</span> <span class="nv">$LOCKFILE</span>
</code></pre></div></div>

<p>It really is as simple as that!</p>

<p>In the case of my example we defined the lock file as a file on the same EFS volume as the repo data was being synced to. This way all the nodes could access the same lock file and know if another node was currently syncing the repos.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="what-if-the-script-fails">What if the script fails?</h2>

<p>This is a good question. If the script fails for any reason then the lock file will remain in place and the next time the script is run it will see the lock file and exit. This is not ideal, and in fact we had this exact issue during my project. There’s a few things that you can do depending on your desired outcome. In my case we were troubleshooting some bad repo definitions and we decided that we wanted to know when the lock file became a blocker for other processes so we did a couple of things:</p>

<ol>
  <li>We wrote into the lock file which node had established the lock and the time it was established. This way we could see which node was causing the issue.</li>
  <li>For any subsequent node that found the lock file we wrote a message to the lock file to say which node had found the lock file and the time it was found. This way we could see which nodes were trying to run the script and when they were trying to run it.</li>
</ol>

<p>This might look something like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="nv">LOCKFILE</span><span class="o">=</span>/tmp/mylockfile <span class="c"># Set the lock file location to whatever path you need</span>

<span class="k">if</span> <span class="o">[</span> <span class="nt">-f</span> <span class="nv">$LOCKFILE</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"Lock file exists, exiting"</span>
    <span class="nb">echo</span> <span class="s2">"Lock file found by </span><span class="si">$(</span><span class="nb">hostname</span><span class="si">)</span><span class="s2"> at </span><span class="si">$(</span><span class="nb">date</span><span class="si">)</span><span class="s2">"</span> <span class="o">&gt;&gt;</span> <span class="nv">$LOCKFILE</span>
    <span class="nb">exit </span>1
<span class="k">fi

</span><span class="nb">touch</span> <span class="nv">$LOCKFILE</span>
<span class="nb">echo</span> <span class="s2">"Lock file established by </span><span class="si">$(</span><span class="nb">hostname</span><span class="si">)</span><span class="s2"> at </span><span class="si">$(</span><span class="nb">date</span><span class="si">)</span><span class="s2">"</span> <span class="o">&gt;&gt;</span> <span class="nv">$LOCKFILE</span>

<span class="c"># Do some work here</span>

<span class="nb">rm</span> <span class="nv">$LOCKFILE</span>
</code></pre></div></div>

<p>This way we could see which node was causing the issue and which nodes were trying to run the script. This helped us to identify the issue and resolve it.</p>

<p>Another strategy, and perhaps one that might make more sense in a production environment would be to use a tool like <code class="language-plaintext highlighter-rouge">trap</code> to catch the exit signal and remove the lock file. This way if the script fails for any reason the lock file will be removed and the next time the script is run it will be able to run.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="nv">LOCKFILE</span><span class="o">=</span>/tmp/mylockfile <span class="c"># Set the lock file location to whatever path you need</span>

<span class="nb">trap</span> <span class="s1">'rm $LOCKFILE'</span> EXIT

<span class="k">if</span> <span class="o">[</span> <span class="nt">-f</span> <span class="nv">$LOCKFILE</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"Lock file exists, exiting"</span>
    <span class="nb">exit </span>1
<span class="k">fi

</span><span class="nb">touch</span> <span class="nv">$LOCKFILE</span>

<span class="c"># Do some work here</span>
</code></pre></div></div>

<p>In this case we don’t need to explicitly remove the lock file as the <code class="language-plaintext highlighter-rouge">trap</code> will catch the exit signal and remove the lock file for us.</p>

<p>If you’re using <code class="language-plaintext highlighter-rouge">trap</code> then I would strongly recommend that you are logging your scripts somewhere else so that you have a way to find and trace an issue if it arises.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="conclusion">Conclusion</h2>

<p>Lock files are a simple but effective way to manage the execution of scripts across multiple nodes. They can also be used to manage execution of processes on the same machine if you need to ensure that only one process is running at a time. The best bit…(?) They can be implemented in a few lines of code as we’ve seen here.</p>

<p>In our case we had a typical ASG deployment of 3 nodes, and 3 collections of repos to be synced. By using lock files we were able to ensure that only one node was syncing a set of repos at a time and that other nodes moved on to process other repo sets. Thus we both avoided race conditions and duplication of work as well as reduced the overall sync by by nearly two thirds compared to the previous design by using the lock files to split the work across the nodes.</p>

<p>If you haven’t already, check out my post <a href="/blog/2024/10/09/systemd-unit-files-for-tasks/">SystemD Unit</a> for a great partner tool for managing and running your scripts and tools on your Linux machines.</p>

<p><a href="#top">Back to top</a></p>

<p>If this article helped inspire you please consider sharing this article with your friends and colleagues, or let me know via <a href="https://www.linkedin.com/in/wattsgraham/">LinkedIn</a> or <a href="https://twitter.com/Ginger_Graham">X / Twitter</a>.  If you have any ideas for further content you might like to see please let me know too.</p>

<p><a href="#top">Back to top</a></p>]]></content><author><name>Graham Watts</name><email>mail@grahamwatts.co.uk</email></author><category term="blog" /><category term="technical" /><category term="linux" /><category term="devops" /><category term="development" /><summary type="html"><![CDATA[Using lock files to manage access to resources or process execution in bash]]></summary></entry><entry><title type="html">Managing Tasks With SystemD</title><link href="https://www.grahamwatts.co.uk/blog/2024/10/09/systemd-unit-files-for-tasks/" rel="alternate" type="text/html" title="Managing Tasks With SystemD" /><published>2024-10-09T00:00:00+01:00</published><updated>2024-10-09T00:00:00+01:00</updated><id>https://www.grahamwatts.co.uk/blog/2024/10/09/systemd-unit-files-for-tasks</id><content type="html" xml:base="https://www.grahamwatts.co.uk/blog/2024/10/09/systemd-unit-files-for-tasks/"><![CDATA[<p>While working on that same project that brought me to look at <a href="/blog/2024/10/09/lock-files-in-bash/">bash lock files</a> I also needed to co-ordinate the running of a number of scripts away from the main thread of the originating task. The context here is that we had a number of scripts to sync yum repositories on an internal mirror that we were creating. We had implemented the <a href="/blog/2024/10/09/lock-files-in-bash/">bash lock files</a> pattern from the linked post on 3 separate scripts that between them handled all of the repos necessary. There were a number of repos to sync and especially on a first launch scenario the sync would take many hours, in the region of 8 when run consecutively on a single node. As discussed in the <a href="/blog/2024/10/09/lock-files-in-bash/">bash lock files</a> post we had 3 nodes in an auto-scaling group and were splitting the workload across all 3 nodes using the lock file pattern. However, a limitation of using an auto-scaling group is that there is a hard limit of 2 hours for the nodes to come up and complete their cloud-init (userdata) tasks. Even split across 3 nodes we couldn’t guarantee that all 3 nodes would sync inside the 2 hour limit. So we needed a way to trigger the scripts to be run in such a way that the cloud-init tasks would complete, even while the repo sync was running.</p>

<p>The approach we came up with was to use a <a href="https://www.freedesktop.org/wiki/Software/systemd/">SystemD</a> unit file to run the scripts. This way we could trigger the service from the cloud-init tasks which would then receive a success signal for the service starting and then the cloud-init tasks could complete. The service would then run the scripts in the background and we could monitor the progress of the scripts using the <code class="language-plaintext highlighter-rouge">journalctl</code> command.</p>

<h2 id="what-is-systemd">What is SystemD?</h2>

<p>I’m not going to get into everything that SystemD is here, you can read all about it <a href="https://systemd.io/">here</a>, in short it’s the core of almost all modern Linux systems today (at the time of writing…) and is responsible for managing the system services and processes. If you’re like me and come from a Windows background and you’ve worked with services and services management through <code class="language-plaintext highlighter-rouge">services.msc</code> then you can think of SystemD as the Linux equivalent for the purposes of our discussion here.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="what-is-a-systemd-unit-file">What is a SystemD unit file?</h2>

<p>A SystemD unit file is a configuration file that defines a service, socket, device, mount point, or other entity that SystemD can manage. The unit files are stored in <code class="language-plaintext highlighter-rouge">/etc/systemd/system/</code> and can be used to start, stop, restart, enable, disable, and otherwise manage the services and processes on a Linux system. See the following documentation for more information on the use of SystemD, SystemD unit files, and SystemD service files:</p>

<ul>
  <li><a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.html#">SystemD</a></li>
  <li><a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#">SystemD Unit Files</a></li>
  <li><a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#">SystemD Service Files</a></li>
</ul>

<p>Note: there are other types of unit file you will also find listed here which may be useful in other scenarios. We may even touch on some of them later in this post.</p>

<p><a href="#top">Back to top</a></p>

<h3 id="so-how-do-we-use-systemd-to-run-our-scripts">So, how do we use SystemD to run our scripts?</h3>

<p>We can use a service file to define the service we want to run. Here’s an example of a service file that we used to run our repo sync scripts:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Unit]</span><span class="w">
</span><span class="py">Description</span><span class="p">=</span><span class="s">Sync Yum Repositories</span>
<span class="w">
</span><span class="nn">[Service]</span><span class="w">
</span><span class="py">Type</span><span class="p">=</span><span class="s">simple</span>
<span class="py">ExecStart</span><span class="p">=</span><span class="s">/usr/local/bin/sync-repos.sh</span>
</code></pre></div></div>

<p>This will would then be saved in <code class="language-plaintext highlighter-rouge">/etc/systemd/system/</code> as <code class="language-plaintext highlighter-rouge">sync-repos.service</code> e.g. <code class="language-plaintext highlighter-rouge">/etc/systemd/system/sync-repos.service</code>. Obviously (hopefully), we can name the service whatever we like, but it’s good practice to name it something that makes sense and is descriptive of what the service does.</p>

<p>All we need to do now is use <code class="language-plaintext highlighter-rouge">systemctl</code> to start the service:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl start sync-repos.service
</code></pre></div></div>

<p>If the service is intended to run on boot we can enable it:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl <span class="nb">enable </span>sync-repos.service
</code></pre></div></div>

<p>And, of course, we can check the status of the service:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl status sync-repos.service
</code></pre></div></div>

<h4 id="what-do-the-options-in-the-service-file-mean">What do the options in the service file mean?</h4>

<p>Please see the docs for a full list of options, but here’s a quick rundown of the options we used in our service file:</p>

<p>The <code class="language-plaintext highlighter-rouge">Description</code> is a description of the service that will be displayed when you run <code class="language-plaintext highlighter-rouge">systemctl status sync-repos.service</code>.</p>

<p>The <code class="language-plaintext highlighter-rouge">Type</code> listed in the service file can be one of the following:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">simple</code></li>
  <li><code class="language-plaintext highlighter-rouge">exec</code></li>
  <li><code class="language-plaintext highlighter-rouge">forking</code></li>
  <li><code class="language-plaintext highlighter-rouge">oneshot</code></li>
  <li><code class="language-plaintext highlighter-rouge">dbus</code></li>
  <li><code class="language-plaintext highlighter-rouge">notify</code></li>
  <li><code class="language-plaintext highlighter-rouge">notify-reload</code></li>
  <li><code class="language-plaintext highlighter-rouge">idle</code></li>
</ul>

<p>Details on what they all mean can here found listed under <a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#Options">Options</a></p>

<p>The <code class="language-plaintext highlighter-rouge">ExecStart</code> is the command that will be run when the service is started. In this case we’re running a script that we’ve saved in <code class="language-plaintext highlighter-rouge">/usr/local/bin/sync-repos.sh</code>. This is effectively the approach we used for <a href="/blog/2024/10/09/lock-files-in-bash/">bash lock files</a>. This gave us the benefit of being able to trigger the script to run from the cloud-init tasks and then check on the status and progress of the script using <code class="language-plaintext highlighter-rouge">journalctl</code> or by grepping <code class="language-plaintext highlighter-rouge">/var/log/messages</code> for the output of the script.</p>

<p>In our use case we actually opted for <code class="language-plaintext highlighter-rouge">oneshot</code> as the <code class="language-plaintext highlighter-rouge">ExecType</code> as we only wanted the script to run once and then exit. We had set the scripts to run using <code class="language-plaintext highlighter-rouge">cron</code> for future runs. We could have used <a href="#timer-units---replacing-cron">Timer Units</a> instead of <code class="language-plaintext highlighter-rouge">cron</code> however the majority of the team are familiar with <code class="language-plaintext highlighter-rouge">cron</code> and many other tasks already configured are using <code class="language-plaintext highlighter-rouge">cron</code> so we decided to stick with it for now.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="timer-units---replacing-cron">Timer Units - replacing cron</h2>

<p>So, I mentioned that we could have replaced <code class="language-plaintext highlighter-rouge">cron</code>… Depending on your viewpoint this might be an exciting proposition, or an act of heresy! I’m not here to get into the politics of <code class="language-plaintext highlighter-rouge">cron</code> vs <code class="language-plaintext highlighter-rouge">SystemD</code> timers or anything else; what I will say though is I’m personally a fan of building as much as possible into the system operations themselves, rather than many additional and bolt-on services. I’m also a fan of consistency and less dated and convoluted approaches to things. You’re entitled to your opinion, but for me if you command structure has some weird, non-standard syntax dating back 40 years, I’m probably going to look for something more modern.</p>

<p>Anyway, back to <a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html#">SystemD Timers</a>! Timers are another type of unit file that can be used to trigger the execution of a service at a specific time or at a specific interval (kind of like <code class="language-plaintext highlighter-rouge">cron</code>). The timer unit file is saved in the same location as the service file, <code class="language-plaintext highlighter-rouge">/etc/systemd/system/</code>, and has the same name as the service file but with a <code class="language-plaintext highlighter-rouge">.timer</code> extension. Here’s an example of a timer file that would run the service every 6 hours:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Unit]</span><span class="w">
</span><span class="py">Description</span><span class="p">=</span><span class="s">Run sync-repos.service every 6 hours</span>
<span class="w">
</span><span class="nn">[Timer]</span><span class="w">
</span><span class="py">OnCalendar</span><span class="p">=</span><span class="s">*-*-* 0/6:00:00</span>
<span class="py">Persistent</span><span class="p">=</span><span class="s">true</span>
<span class="py">Unit</span><span class="p">=</span><span class="s">sync-repos.service</span>
<span class="w">
</span><span class="nn">[Install]</span><span class="w">
</span><span class="py">WantedBy</span><span class="p">=</span><span class="s">timers.target</span>
</code></pre></div></div>

<p>This file would be saved as <code class="language-plaintext highlighter-rouge">/etc/systemd/system/sync-repos.timer</code>.</p>

<p>The <code class="language-plaintext highlighter-rouge">OnCalendar</code> option is used to define when the service should run. In this case we’re using <code class="language-plaintext highlighter-rouge">*-*-* 0/6:00:00</code> which means every 6 hours.</p>

<p>The <code class="language-plaintext highlighter-rouge">Persistent</code> option is used to ensure that if the system is down when the timer is due to run, the service will run as soon as the system is back up.</p>

<p>The <code class="language-plaintext highlighter-rouge">Unit</code> option is used to define which service the timer is triggering.</p>

<p>The <code class="language-plaintext highlighter-rouge">WantedBy</code> option is used to define which target the timer is associated with. In this case we’re using <code class="language-plaintext highlighter-rouge">timers.target</code> which is the target for timer units.</p>

<p>You can find more details here: <a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html#">SystemD Timer Units</a></p>

<p><a href="#top">Back to top</a></p>

<h3 id="starting-services-when-were-using-timers">Starting services when we’re using timers</h3>

<p>If you’re using a timer to trigger the service then you’ll need to make sure that the timer is enabled and started. You can do this using the following commands:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl <span class="nb">enable </span>sync-repos.timer
systemctl start sync-repos.timer
</code></pre></div></div>

<p>Note: this enables the timer, not the service. The service will be triggered by the timer when the next scheduled time is reached. If you want the service to run immediately you can start the service directly:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl start sync-repos.service
</code></pre></div></div>

<p>If you’ve already enabled and started the timer then the service will run at the next scheduled time, as well as now.</p>

<p>You can get the status of the timer using:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl status sync-repos.timer
</code></pre></div></div>

<p>And you can get the status of the service using:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl status sync-repos.service
</code></pre></div></div>

<p><a href="#top">Back to top</a></p>

<h2 id="conclusion">Conclusion</h2>

<p>SystemD is a powerful tool for managing services and processes on a Linux system. It can be used to run scripts and services in the background and can be used to trigger services at specific times or intervals. It can be used to replace <code class="language-plaintext highlighter-rouge">cron</code> for running scripts and services at specific times or intervals and can be used to manage the execution of scripts and services in a more modern and consistent way.</p>

<p>So next time your project needs to run complex tasks during launch, such as AWS cloud-init, and you need to run scripts in the background, or want to set up complex schedules for running scripts, consider using SystemD to manage the execution of your scripts and services.</p>

<p><a href="#top">Back to top</a></p>

<p>If this article helped inspire you please consider sharing this article with your friends and colleagues, or let me know via <a href="https://www.linkedin.com/in/wattsgraham/">LinkedIn</a> or <a href="https://twitter.com/Ginger_Graham">X / Twitter</a>.  If you have any ideas for further content you might like to see please let me know too.</p>

<p><a href="#top">Back to top</a></p>]]></content><author><name>Graham Watts</name><email>mail@grahamwatts.co.uk</email></author><category term="blog" /><category term="technical" /><category term="linux" /><category term="devops" /><category term="development" /><summary type="html"><![CDATA[Using SystemD to run tasks on Linux systems]]></summary></entry><entry><title type="html">Managing Secrets In Linux</title><link href="https://www.grahamwatts.co.uk/gnome-secrets/" rel="alternate" type="text/html" title="Managing Secrets In Linux" /><published>2024-04-01T00:00:00+01:00</published><updated>2025-11-30T00:00:00+00:00</updated><id>https://www.grahamwatts.co.uk/linux-secret-management</id><content type="html" xml:base="https://www.grahamwatts.co.uk/gnome-secrets/"><![CDATA[<p>I recently discovered some new ways to use a tool I’d started with a long time back and subsequently had forgotten pretty much all about. So, now that I’ve learnt some more it’s time to share what I’ve learnt and what I was doing before. Today’s topic is all about managing secrets on your local machine when working on Linux. To be more specific I’m really talking about GNOME based distributions like Ubuntu, and Fedora (default desktops) or any other distribution that has a GNOME based spin. That said, this being Linux, the chances are that you can use these tools on any distribution with a little work or modification.</p>

<p>First, some context of what I was trying to do. I have a few services on remote servers, things like <a href="https://www.hashicorp.com/products/vault">Hashicorp Vault</a> and a self-hosted instance of <a href="https://about.gitlab.com/install/">GitLab</a> that I need to access from my local workstation using the CLI. For those less familiar, these tools often require a Personal Access Token (PAT) to authenticate in place of a password when using multi-factor authentication (MFA). I don’t want to be prompted for these secrets every time I run a command, so I was looking for a way to store these secrets on my machine.</p>

<h2 id="how-not-to-do-it">How not to do it</h2>

<p>A common pattern that a lot of tools refer to is storing these secrets in environment variables such as <code class="language-plaintext highlighter-rouge">VAULT_TOKEN</code> or <code class="language-plaintext highlighter-rouge">GITLAB_TOKEN</code>.  The problem is… where do these variables come from? A common approach to address this is to <code class="language-plaintext highlighter-rouge">export</code> these variables in your shell profile, such as <code class="language-plaintext highlighter-rouge">~/.bashrc</code> or <code class="language-plaintext highlighter-rouge">~/.zshrc</code>. This works, but it’s terrible for security as these secrets are now stored in plain text on your machine. This might look like:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># ~/.bashrc</span>
<span class="nb">export </span><span class="nv">VAULT_TOKEN</span><span class="o">=</span><span class="s2">"s.123abc456def789"</span>
<span class="nb">export </span><span class="nv">GITLAB_TOKEN</span><span class="o">=</span><span class="s2">"890def456abc123"</span>
</code></pre></div></div>

<p>Not only are they stored in plain text but they are also stored in a file everyone knows about, trivially easy to find and read.</p>

<p>So maybe you’re thinking, “I’ll use some other file that’s not so obvious”. You could use a file like <code class="language-plaintext highlighter-rouge">~/.secrets</code>  or <code class="language-plaintext highlighter-rouge">~/.not_secrets</code> and <code class="language-plaintext highlighter-rouge">source</code> that file in your shell profile. This is a fraction better, but still not great. This is because the file is still in plain text and still in a location that can be found by simply reading the commonly known shell profile file. This might look like:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># ~/.bashrc</span>
<span class="nb">source</span> ~/.secrets
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># ~/.secrets</span>
<span class="nb">export </span><span class="nv">VAULT_TOKEN</span><span class="o">=</span><span class="s2">"s.123abc456def789"</span>
<span class="nb">export </span><span class="nv">GITLAB_TOKEN</span><span class="o">=</span><span class="s2">"890def456abc123"</span>
</code></pre></div></div>

<p>What I was looking for was a way to store these secrets in a secure way on my machine, and have them accessible in the CLI for commands and scripts.</p>

<h2 id="enter-keyrings">Enter Keyrings</h2>

<p>Bearing mind all this started from Vault , wouldn’t it be useful if we had some local copy of vault to store our secrets? But Vault is rather complicated and cumbersome for a local install and would have exactly the same issue needing a token to access it to get our tokens. So, what if we could use something that’s already on our machine and is designed to store secrets? Enter <a href="https://en.wikipedia.org/wiki/Keyring_(cryptography)">keyrings</a>. Keyrings are a secure way to store secrets on your machine. They are encrypted and can only be accessed by the user that created them. Tools like <a href="https://man7.org/linux/man-pages/man7/keyrings.7.html"><code class="language-plaintext highlighter-rouge">keyrings</code></a> and <a href="https://wiki.gnome.org/Projects/GnomeKeyring"> <code class="language-plaintext highlighter-rouge">gnome-keyring</code></a> offer this kind of functionality.</p>

<p>Being that I’m using Fedora, a GNOME based distribution, I have <code class="language-plaintext highlighter-rouge">gnome-keyring</code> installed by default, also as best I could tell from my limited reading before settling for the installed solution it looks like <code class="language-plaintext highlighter-rouge">keyrings</code> might default to storing values in memory for the duration of a session. <code class="language-plaintext highlighter-rouge">gnome-keyring</code> definitely stores values persistently on disk which is what I was looking for, so I got stuck in to <code class="language-plaintext highlighter-rouge">gnome-keyring</code> as my solution, please do your own research with regard to <code class="language-plaintext highlighter-rouge">keyrings</code> if you’re not using a GNOME based distro or want to use a less GNOME specific solution. Also, if you’re on a headless system without a desktop environment some of what we cover here may not be the right solution for you, but for anyone on a GUI workstation, likely based on GNOME, this should be a good solution.</p>

<h2 id="secure-those-secrets-with-gnome-keyring">Secure those secrets with <code class="language-plaintext highlighter-rouge">gnome-keyring</code></h2>

<p>There is a command line tool for interacting with <code class="language-plaintext highlighter-rouge">gnome-keyring</code> called <code class="language-plaintext highlighter-rouge">secret-tool</code>. This tool allows you to store, retrieve, and delete secrets from your keyring. The basic usage is:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>secret-tool store <span class="nt">--label</span><span class="o">=</span><span class="s2">"My Secret"</span> key value
</code></pre></div></div>

<p>The tool will then prompt you for the “password” to be stored.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>secret-tool store <span class="nt">--label</span><span class="o">=</span><span class="s2">"My Secret"</span> key value
Password: 
</code></pre></div></div>

<p>The first thing that threw me here was this key and value that I needed to pass and what they actually did. My initial thinking was that the key would be similar to the label and the value would be the secret. So you don’t join my confusion this is <strong>not</strong> the case. The key and value are a unique pair to identify the secret for lookup. So, bearing in mind that we are storing tokens we might decide to call the “key” <code class="language-plaintext highlighter-rouge">token</code> and then the “value” might be the service name, so to store the Vault and GitLab tokens we store the values like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>secret-tool store <span class="nt">--label</span><span class="o">=</span><span class="s2">"Vault Token"</span> token vault
Password:

<span class="nv">$ </span>secret-tool store <span class="nt">--label</span><span class="o">=</span><span class="s2">"GitLab Token"</span> token gitlab
Password:
</code></pre></div></div>

<p>In both cases we’d pass the actual value of the token to the <code class="language-plaintext highlighter-rouge">Password:</code> prompt.</p>

<p>The <code class="language-plaintext highlighter-rouge">--label</code> flag is optional, but it’s useful for identifying the secret when you want to retrieve it, especially when using the GUI front end to <code class="language-plaintext highlighter-rouge">gnome-keyring</code> which we’ll get to in a moment.</p>

<h2 id="retrieving-secrets-with-secret-tool">Retrieving secrets with <code class="language-plaintext highlighter-rouge">secret-tool</code></h2>

<p>Now that we’ve stored our secrets, we probably need to retrieve them at some point to do something useful with them. To retrieve a secret we need to know the key/token pair that we used to store it. We can then use the <code class="language-plaintext highlighter-rouge">secret-tool</code> command to retrieve the secret:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>secret-tool lookup key token
</code></pre></div></div>

<p>So to retrieve the Vault token we’d use:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>secret-tool lookup key vault
</code></pre></div></div>

<p>This will output the token to the terminal.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>secret-tool lookup key vault
<span class="nv">$ </span>s.123abc456def789
</code></pre></div></div>

<p>If you want more information about the secret you can use the <code class="language-plaintext highlighter-rouge">search</code> command, again search for the key/token pair:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>secret-tool search key token
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>secret-tool search <span class="nt">--all</span> key demo-01               
<span class="o">[</span>/131]
label <span class="o">=</span> Demo-01
secret <span class="o">=</span> hello blog
created <span class="o">=</span> 2024-04-01 16:02:27
modified <span class="o">=</span> 2024-04-01 16:02:27
schema <span class="o">=</span> org.freedesktop.Secret.Generic
attribute.key <span class="o">=</span> demo-01
</code></pre></div></div>

<h2 id="updating-and-deleting-secrets">Updating and deleting secrets</h2>

<p>To update a secret with a new value all you need to do is store the secret again with the same key/token pair. The <code class="language-plaintext highlighter-rouge">secret-tool</code> command will overwrite the existing secret with the new value.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>secret-tool store <span class="nt">--label</span><span class="o">=</span><span class="s2">"Demo-01"</span> key demo-01
Password: <span class="c"># "hello blog"</span>

<span class="nv">$ </span>secret-tool lookup key demo-01
hello blog

<span class="nv">$ </span>secret-tool store <span class="nt">--label</span><span class="o">=</span><span class="s2">"Demo-01"</span> key demo-01
Password: <span class="c"># "Hello Blog"</span>

<span class="nv">$ </span>secret-tool lookup key demo-01
Hello Blog
</code></pre></div></div>

<p>To delete a secret you can use the <code class="language-plaintext highlighter-rouge">clear</code> command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>secret-tool clear key token
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>secret-tool clear key demo-01
</code></pre></div></div>

<h2 id="using-a-secret-in-a-script">Using a secret in a script</h2>

<p>OK, great, so we now have a way to store and retrieve secrets on our local machine. But how do we use these secrets in a script?</p>

<p>Well the <code class="language-plaintext highlighter-rouge">secret-tool lookup</code> command is our friend here and we can either use it directly in a script or we can use it to set an environment variable in our shell profile.  Directly in a script would minimise the exposure of the secret, but it may be practical to be shared between scripts and/or users so setting an environment variable might be something to consider.</p>

<p>In a script it might look like:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="nb">local </span><span class="nv">VAULT_TOKEN</span><span class="o">=</span><span class="si">$(</span>secret-tool lookup key vault<span class="si">)</span>
</code></pre></div></div>

<p>In your shell profile it might look like:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># ~/.bashrc</span>
<span class="nb">export </span><span class="nv">VAULT_TOKEN</span><span class="o">=</span><span class="si">$(</span>secret-tool lookup key vault<span class="si">)</span>
</code></pre></div></div>

<p>I used to trend towards the environment variable approach as it was quicker for me to get my head around originally, and I also take a number of other precautions to secure my machine and the secrets stored on it. However, more recently I’ve started to prefer using the command directly in scripts to minimize the exposure of secrets.</p>

<h2 id="using-the-gui">Using the GUI</h2>

<p>If you’re not a fan of the command line, or you just want to see what’s in your keyring, you can use the GUI front end to <code class="language-plaintext highlighter-rouge">gnome-keyring</code>. To access the GUI you can use the <code class="language-plaintext highlighter-rouge">seahorse</code> command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>seahorse
</code></pre></div></div>

<p>You can also find this as “Passwords and Keys” in the GNOME apps; although you may need to install the <code class="language-plaintext highlighter-rouge">seahorse</code> package to get this functionality.</p>

<p>To see this in action let’s quickly create a demo secret using the command line and then view it in the GUI.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>secret-tool store <span class="nt">--label</span><span class="o">=</span><span class="s2">"Demo-01"</span> key demo-01
Password: <span class="c"># "hello blog"</span>

<span class="c"># Prove it's stored by looking it up on the command line</span>
<span class="nv">$ </span>secret-tool lookup key demo-01
hello blog

<span class="c"># Open the GUI</span>
<span class="nv">$ </span>seahorse <span class="c"># Or navigate to "Passwords and Keys" in the GNOME apps</span>
</code></pre></div></div>

<p>Once the GUI is open you can search for your secret by the label,hint it’ll most likely be in the “Login” keyring. You can then view the secret by double clicking on it and you’ll end up with something like this:</p>

<p><img src="../../assets/images/blog/gnome-passwords-and-keys-demo-01.webp" alt="GNOME Passwords &amp; Keys / a.k.a. Seahorse" title="A screenshot of the GNOME Passwords &amp; Keys / Seahorse application showing a secret stored in the keyring" /></p>

<p>In the above image we can see 2 secrets stored in the keyring, the first is the <code class="language-plaintext highlighter-rouge">Demo-01</code> secret we just stored and the second is <code class="language-plaintext highlighter-rouge">Demo-02 </code> which I stored earlier. I have double clicked on the <code class="language-plaintext highlighter-rouge">Demo-01</code> secret to show the details of the secret. You can see the label, the obscured secret, and the key/token pair that we used to store the secret. You can also copy the value of the secret to the clipboard from here.</p>

<h2 id="what-else-can-you-do-with-gnome-keyring">What else can you do with <code class="language-plaintext highlighter-rouge">gnome-keyring</code>?</h2>

<p><code class="language-plaintext highlighter-rouge">gnome-keyring</code> is a powerful tool and can be used for more than just storing secrets. You can store SSH keys, GPG keys, and other certificates in your keyring. You can also use the <code class="language-plaintext highlighter-rouge">seahorse</code> GUI to manage these keys.</p>

<p>The function I previously used and really stopped thinking about was that I use <code class="language-plaintext highlighter-rouge">ssh-add</code> a lot to add my SSH keys to the keyring so I don’t have to enter my passphrase every time I use my SSH keys. This is a great feature and I highly recommend using it if you’re not already.</p>

<h2 id="anything-else-to-note">Anything else to note?</h2>

<p>One thing to note is that the <code class="language-plaintext highlighter-rouge">gnome-keyring</code> daemon needs to be running for the <code class="language-plaintext highlighter-rouge">secret-tool</code> command to work. This is usually started when you log in to your GNOME session, but if you’re running scripts that use <code class="language-plaintext highlighter-rouge">secret-tool</code> you may need to start the daemon manually. You can do this with the <code class="language-plaintext highlighter-rouge">gnome-keyring-daemon</code> command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gnome-keyring-daemon
</code></pre></div></div>

<p>For most users though the daemon will run automatically when you log in to your GNOME session. If you don’t log in to the session with a password, for example you have biometric logins enabled or a smart card, or you’ve set up automatic login, then you may be prompted for a password saying that the keyring is locked. This is because the keyring is locked with your login password and needs to be unlocked before you can access the secrets stored in it. Typically you can just enter your password when prompted soon after logging in and the keyring will be unlocked for the session.</p>

<p>If for some reason it doesn’t unlock automatically you can use the <code class="language-plaintext highlighter-rouge">seahorse</code> GUI to unlock the keyring. You can do this by right clicking on the keyring in the GUI and selecting “Unlock”. You’ll then be prompted for your login password. Or you can use the command line with <code class="language-plaintext highlighter-rouge">gnome-keyring-daemon</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gnome-keyring-daemon <span class="nt">--unlock</span>
</code></pre></div></div>

<p>Or</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gnome-keyring-daemon <span class="nt">--login</span>
</code></pre></div></div>

<h2 id="update-looking-for-ways-to-work-with-secrets-on-windows">Update: Looking for ways to work with secrets on Windows?</h2>

<p>If you’re looking for ways to work with secrets on Windows, check out my post on <a href="/windows-secrets/">Managing Secrets in Windows</a>.</p>

<h2 id="update-looking-for-ways-to-work-with-secrets-on-macos">Update: Looking for ways to work with secrets on macOS?</h2>

<p>If you’re looking for ways to work with secrets on macOS, check out my post on <a href="/macos-secrets/">Managing Secrets in macOS</a>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>So, that’s a quick look at how you can manage secrets on your local machine when working on Linux. I hope you found this useful and that it helps you to secure your secrets on your machine. If you have any questions or comments please feel free to reach out to me on <a href="https://www.linkedin.com/in/wattsgraham/">LinkedIn</a> or <a href="https://twitter.com/Ginger_Graham">X / Twitter</a>. Also, please feel free to share this post with anyone you think might find it useful, you’ll find the share buttons below.</p>

<p><a href="#top">Back to top</a></p>]]></content><author><name>Graham Watts</name><email>mail@grahamwatts.co.uk</email></author><category term="blog" /><category term="technical" /><category term="linux" /><category term="devops" /><category term="development" /><category term="security" /><summary type="html"><![CDATA[A look at some tools to manage secrets when working on linux]]></summary></entry><entry><title type="html">Introducing Regular Expressions</title><link href="https://www.grahamwatts.co.uk/regex/" rel="alternate" type="text/html" title="Introducing Regular Expressions" /><published>2024-01-01T00:00:00+00:00</published><updated>2024-01-01T00:00:00+00:00</updated><id>https://www.grahamwatts.co.uk/regular-expressions</id><content type="html" xml:base="https://www.grahamwatts.co.uk/regex/"><![CDATA[<p>Regular expressions are a powerful tool for searching and manipulating text. They are used in many programming languages, text editors, and other tools. They are also a source of confusion and frustration for many people. My aim with this article is to demystify regular expressions and help you understand how to use them by introducing the concepts and terminology used, demonstrate some examples, and then point you to some resources for further learning.</p>

<p>Through this article we’ll look at a few examples.  If you want to explore along as we go you can add the example text discussed in each example to a simple text file and then use a tool like PowerShell <code class="language-plaintext highlighter-rouge">Select-String</code> on Windows, or <code class="language-plaintext highlighter-rouge">grep</code> on Linux and MacOS to practice searching, later on in <a href="#getting-some-practice">Getting Some Practice</a> I have some links to practice materials I’ve put together for you too.  Alternatively, a tool like <a href="https://regex101.com/">regex101.com</a> is a great way to experiment with regular expressions and see the results in real time.</p>

<h2 id="introduction">Introduction</h2>

<p>If you’ve spent any time around large volumes of log files, or programming where you need to validate incoming text data then you’ve likely stumbled across references to “just use a regex” or words to that effect. You then get presented with impenetrable strings of characters that look like they’ve been generated by a cat walking across a keyboard such as these <code class="language-plaintext highlighter-rouge">(w)o\1</code> or <code class="language-plaintext highlighter-rouge">\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b</code> and you reach for the paracetamol, a stiff drink and look for another way. This is the way most people, in my experience, start off with regular expressions and it’s no wonder that they strike fear and terror into the hearts of many. Ten points for anyone who can already explain what these examples mean…</p>

<p>So, in this article I want to relieve those headaches, remove the fears, and take you through what all the chaos means and how you can use regular expressions to make your life easier!</p>

<h2 id="why-use-regular-expressions">Why use regular expressions?</h2>

<p>Before we get into anything technical, let’s start by understanding the problems regular expressions can help us solve. I’ll start by listing some common problems anyone working in tech for long enough will likely encounter:</p>

<ul>
  <li>Searching through a large volume of text, typically log files, looking for a specific pattern of text. For example, you need to find every IP address that has connected to a web server in the last 24 hours, or the time and date that a specific user logged in.
    <ul>
      <li>Perhaps you also need to reformat that log data to produce a report of just the information you’re interested in from the extensive log data</li>
    </ul>
  </li>
  <li>You need to validate that a user has entered a valid email address, or phone number, or credit card number, or any other type of data that has a specific format.</li>
  <li>You need to update or refactor some code to change every reference to a specific name or variable to a new name or variable. Maybe your company was acquired, and you need to replace all instances of “My Company”, or “MyCompany” with “New Company”.</li>
</ul>

<p>Remember also that regular expressions are just a tool, and they have a specific purpose and problem to solve. They’re not the solution to all problems, and they’re not always the best tool for the job. In some cases, other options, specific parsers for example, exist and may well be a better choice. Particularly when programmatically parsing data from sources such as JSON or XML many languages have parsing libraries built in, or available, which will be a much more robust and easier to maintain solution than a regular expression.</p>

<p>All that said, for situations such as the ones above, regular expressions are a great tool to have in your toolbox, so let’s explore them in more detail.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="what-is-a-regular-expression">What is a regular expression?</h2>

<p>As with any good story we should start at the beginning. Before we even describe regular expressions let’s talk about names and abbreviations, as there are many in circulation all referring to the same thing. Regular expressions are also known as regex, RegEx, Reg Ex, regexp, or RE to name the common variations I see. I’ll use the terms regular expression or regex throughout this article, but you may see any of the other terms used elsewhere.</p>

<p>So, what is a regular expression? A regular expression is a sequence of characters to represent a known pattern of data without needing to know the exact content of any single specific instance of that pattern. That’s a bit of a mouthful, so let’s break it down.</p>

<p>Telephone numbers in the USA are 10 digits long and typically grouped as 3 digits, followed by a dash or hyphen, another 3 digits, another dash/hyphen, and then a final 4 digit grouping. E.g. 800-555-1234 or 777-555-2345. We know the pattern, but to search for every possible combination in a dataset would be extremely time consuming and error prone if we had to do it manually. How would you approach it?</p>

<p>Similarly entities like emails address or IPv4 addresses have a very clear, known pattern, but the sheer number of permutations of valid addresses makes it impractical to search for them manually.</p>

<p>Regular expressions allow us to describe the pattern of data we’re looking for, and then search for that pattern in a dataset without needing to know the exact content of any specific instance of that pattern. They can include, or exclude, latin alphabet characters, numbers, punctuation, and other special characters. They can also include or exclude whitespace, and can be case sensitive or case insensitive. They can be as simple as a single character, or as complex as you can imagine. As we get more understanding and more skilled they can also be used to replace, or substitute, text in a dataset with other text.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="where-can-i-use-regular-expressions">Where can I use regular expressions?</h2>

<p>The answer is… lot’s of places. Regular expressions are supported in many programming languages, text editors, and other tools. I’ll list some of the more common ones below, but this is by no means an exhaustive list.</p>

<ul>
  <li>Visual Text Editors and IDEs
    <ul>
      <li>Visual Studio Code</li>
      <li>Notepad++</li>
      <li>Sublime Text</li>
      <li>Atom</li>
    </ul>
  </li>
  <li>Command Line Tools and Text Editors
    <ul>
      <li>grep</li>
      <li>sed</li>
      <li>awk</li>
      <li>vi</li>
      <li>vim</li>
      <li>PowerShell Select-String</li>
      <li>Windows Command Line findstr</li>
    </ul>
  </li>
  <li>Programming Lanuages
    <ul>
      <li>JavaScript</li>
      <li>Python</li>
      <li>C#</li>
      <li>Java</li>
      <li>etc.</li>
    </ul>
  </li>
  <li>Cloud Tools
    <ul>
      <li>AWS CloudWatch Logs</li>
      <li>Azure Monitor Logs</li>
      <li>etc.</li>
    </ul>
  </li>
</ul>

<p><a href="#top">Back to top</a></p>

<h2 id="the-regular-expression-character-set">The regular expression character set</h2>

<p>Regular expressions are made up of a set of characters, each with a specific meaning.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Character</th>
      <th style="text-align: center">Meaning</th>
      <th style="text-align: center">Character</th>
      <th style="text-align: center">Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">abc...</code></td>
      <td style="text-align: center">Lowercase alphabet</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">\s</code></td>
      <td style="text-align: center">Any whitespace character</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">ABC...</code></td>
      <td style="text-align: center">Uppercase alphabet</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">\S</code></td>
      <td style="text-align: center">Any non-whitespace character</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">123...</code></td>
      <td style="text-align: center">Numerical digits</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">\A</code></td>
      <td style="text-align: center">Start of string</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">\d</code></td>
      <td style="text-align: center">Any numerical digit</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">\Z</code></td>
      <td style="text-align: center">End of string</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">\D</code></td>
      <td style="text-align: center">Any non-numerical digit</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">\b</code></td>
      <td style="text-align: center">Word boundary</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">\w</code></td>
      <td style="text-align: center">Any alphanumeric character</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">\B</code></td>
      <td style="text-align: center">Not a word boundary</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">\W</code></td>
      <td style="text-align: center">Any non-alphanumeric character</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">*</code></td>
      <td style="text-align: center">Zero or more of the preceding character</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">.</code></td>
      <td style="text-align: center">Any character</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">+</code></td>
      <td style="text-align: center">One or more of the preceding character</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">\</code></td>
      <td style="text-align: center">Escape character</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">?</code></td>
      <td style="text-align: center">Preceding character is optional</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">.</code></td>
      <td style="text-align: center">A literal period character</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">{n}</code></td>
      <td style="text-align: center">Exactly n of the preceding character</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">[abc]</code></td>
      <td style="text-align: center">Any character in the set a, b, or c</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">{n,}</code></td>
      <td style="text-align: center">n or more of the preceding character</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">[^abc]</code></td>
      <td style="text-align: center">Any character not in the set a, b, or c</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">{n,m}</code></td>
      <td style="text-align: center">Between n and m of the preceding character</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">[a-z]</code></td>
      <td style="text-align: center">Any character in the range a to z</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">{,m}</code></td>
      <td style="text-align: center">Up to m of the preceding character</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">[0-9]</code></td>
      <td style="text-align: center">Any digit in the range 0 to 9</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">(...)</code></td>
      <td style="text-align: center">Capture group</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">^</code></td>
      <td style="text-align: center">Start of line</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">(?:...)</code></td>
      <td style="text-align: center">Non-capturing group</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">$</code></td>
      <td style="text-align: center">End of line</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">(a(bc))</code></td>
      <td style="text-align: center">Nested capture group</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">\1</code></td>
      <td style="text-align: center">Contents of first capture group</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">(abc\|def)</code></td>
      <td style="text-align: center">Match abc or def</td>
    </tr>
  </tbody>
</table>

<h3 id="gotchas">Gotchas</h3>

<p>There are a few caveats and gotchas to be aware of when working with regular expressions. Some of the more common ones are:</p>

<ul>
  <li>Regular expressions are case sensitive by default. This means that if you search for “abc” it will match “abc” but not “ABC”. This can be changed in some tools and languages, but not all.
    <ul>
      <li>Check you specific tool or language documentation for details</li>
      <li>Or use <code class="language-plaintext highlighter-rouge">[a-zA-Z]</code> to match both upper and lower case characters for example</li>
    </ul>
  </li>
  <li>Not all languages, particularly older implementations, support all of the characters in the table above. For example, some languages don’t support the <code class="language-plaintext highlighter-rouge">\A</code> and <code class="language-plaintext highlighter-rouge">\Z</code> characters.
    <ul>
      <li>A common one I see is that <code class="language-plaintext highlighter-rouge">grep -E</code> does not support the <code class="language-plaintext highlighter-rouge">\d</code> character, but <code class="language-plaintext highlighter-rouge">grep -P</code> does</li>
    </ul>
  </li>
  <li>Some tools require the regex to be bounded by marker or delimiting characters such as <code class="language-plaintext highlighter-rouge">/</code> or <code class="language-plaintext highlighter-rouge">%</code>
    <ul>
      <li>I see this in my daily work when working with AWS CloudWatch Logs</li>
      <li>This varies by tool so check the documentation for the tool you are using</li>
    </ul>
  </li>
</ul>

<p><a href="#top">Back to top</a></p>

<h3 id="greedy-vs-lazy">Greedy vs. Lazy</h3>

<p>A common challenge when starting out with regex is trying to get the match you want when the text has similar patterns in it. Take, for example, the below demonstrations html excerpt:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;p&gt;</span>Hello<span class="nt">&lt;/p&gt;&lt;span&gt;</span>Awesome<span class="nt">&lt;/span&gt;&lt;p&gt;</span>World<span class="nt">&lt;/p&gt;</span>
</code></pre></div></div>

<p>Say that we want to match the text between from the first <code class="language-plaintext highlighter-rouge">&lt;p&gt;</code> and to the end of the first <code class="language-plaintext highlighter-rouge">&lt;/p&gt;</code>, i.e. we want to match <code class="language-plaintext highlighter-rouge">&lt;p&gt;Hello&lt;/p&gt;</code>. We could try to use the following regex to do that:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;p&gt;<span class="o">(</span>.<span class="k">*</span><span class="o">)</span>&lt;/p&gt;
</code></pre></div></div>

<p>However this will actually match the entire string up until the last <code class="language-plaintext highlighter-rouge">&lt;/p&gt;</code> as shown below:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;p&gt;</span>Hello<span class="nt">&lt;/p&gt;&lt;span&gt;</span>Awesome<span class="nt">&lt;/span&gt;&lt;p&gt;</span>World<span class="nt">&lt;/p&gt;</span>
</code></pre></div></div>

<p>This is because the <code class="language-plaintext highlighter-rouge">.*</code> is “greedy” and will match as much as possible. However, we can use the optional character <code class="language-plaintext highlighter-rouge">?</code> to make the match “lazy” and only match as little as possible. This is shown below:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;p&gt;<span class="o">(</span>.<span class="k">*</span>?<span class="o">)</span>&lt;/p&gt;
</code></pre></div></div>

<p>Which would match the following:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;p&gt;</span>Hello<span class="nt">&lt;/p&gt;</span>
</code></pre></div></div>

<p>I won’t try and get into the technical details of why this happens, but if you’re interested you can read a much better write up than I could do here: <a href="https://blog.kiprosh.com/regular-expressions-greedy-vs-non-greedy/">https://blog.kiprosh.com/regular-expressions-greedy-vs-non-greedy/</a></p>

<p><a href="#top">Back to top</a></p>

<h3 id="special-characters-and-delimiters">Special characters and delimiters</h3>

<p>Some characters have special meaning in regular expressions, and so need to be escaped with a <code class="language-plaintext highlighter-rouge">\</code> character to be matched literally. For example, if you want to match a literal <code class="language-plaintext highlighter-rouge">.</code> character you would need to escape it with a <code class="language-plaintext highlighter-rouge">\</code> character like this <code class="language-plaintext highlighter-rouge">\.</code>. This is because the <code class="language-plaintext highlighter-rouge">.</code> character has a special meaning in regular expressions, and so needs to be escaped to be matched literally. This is also true of other characters such as <code class="language-plaintext highlighter-rouge">*</code>, <code class="language-plaintext highlighter-rouge">+</code>, <code class="language-plaintext highlighter-rouge">?</code>, <code class="language-plaintext highlighter-rouge">(</code>, <code class="language-plaintext highlighter-rouge">)</code>, <code class="language-plaintext highlighter-rouge">[</code>, <code class="language-plaintext highlighter-rouge">]</code>, <code class="language-plaintext highlighter-rouge">{</code>, <code class="language-plaintext highlighter-rouge">}</code>, <code class="language-plaintext highlighter-rouge">^</code>, <code class="language-plaintext highlighter-rouge">$</code>, <code class="language-plaintext highlighter-rouge">\</code>, <code class="language-plaintext highlighter-rouge">|</code>, and <code class="language-plaintext highlighter-rouge">/</code>.</p>

<p>As an example, imagine that you wanted to match the text “Hello World?”. You could try matching the regex <code class="language-plaintext highlighter-rouge">Hello World?</code> as a literal representation of the text you wished to match; however you might get some unexpected results as “Hello Worl” and “Hello World” would match, but not explicitly “Hello World?”. This is because the <code class="language-plaintext highlighter-rouge">?</code> character has a special meaning in regular expressions, as we’ve seen above it makes the preceding character optional. So by including <code class="language-plaintext highlighter-rouge">d?</code> we’ve expressed that the <code class="language-plaintext highlighter-rouge">d</code> character is optional, hence matching “Hello Worl” as well as “Hello World” and not explicitly matching with the <code class="language-plaintext highlighter-rouge">?</code> character.</p>

<p>To ensure that we match the <code class="language-plaintext highlighter-rouge">?</code> character literally we need to escape it with a <code class="language-plaintext highlighter-rouge">\</code> character like this <code class="language-plaintext highlighter-rouge">Hello World\?</code>. This will match the text “Hello World?” explicitly.</p>

<p><a href="#top">Back to top</a></p>

<h3 id="anchors-and-boundaries">Anchors and boundaries</h3>

<p>Anchors and boundaries are special characters that allow us to match specific locations in a string. The most common ones are <code class="language-plaintext highlighter-rouge">^</code> and <code class="language-plaintext highlighter-rouge">$</code> which match the start and end of a string respectively. For example, if we wanted to match the text “Hello World” at the start of a string we could use the regex <code class="language-plaintext highlighter-rouge">^Hello World</code>. This would match the following:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Hello World is awesome
</code></pre></div></div>

<p>But would not match the following:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>This is awesome, Hello World
</code></pre></div></div>

<p>Similarly, if we wanted to match the text “Hello World” at the end of a string we could use the regex <code class="language-plaintext highlighter-rouge">Hello World$</code>. This would match the following:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>This is awesome, Hello World
</code></pre></div></div>

<p>But would not match the following:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Hello World is awesome
</code></pre></div></div>

<p>Other common anchors and boundaries are <code class="language-plaintext highlighter-rouge">\A</code> and <code class="language-plaintext highlighter-rouge">\Z</code> which match the start and end of a string respectively, but do not match the start and end of a line. This is useful when working with multi-line strings, such as log files, where you want to match the start and end of the entire string, but not the start and end of each line.</p>

<p>You can also use <code class="language-plaintext highlighter-rouge">\b</code> and <code class="language-plaintext highlighter-rouge">\B</code> to match word boundaries. For example, if you wanted to match the text “Hello World” but not “Hello Worldly” you could use the regex <code class="language-plaintext highlighter-rouge">\bHello World\b</code> which would require a word boundary before and after the text “Hello World”.</p>

<p>You can also use the <code class="language-plaintext highlighter-rouge">\s</code> to match any whitespace character as a boundary, and <code class="language-plaintext highlighter-rouge">\S</code> to match any non-whitespace character as a boundary.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="simple-examples">Simple examples</h2>

<ul>
  <li>A regex of <code class="language-plaintext highlighter-rouge">a{3}</code> would match 3 consecutive <code class="language-plaintext highlighter-rouge">a</code> characters
    <ul>
      <li>E.g. <code class="language-plaintext highlighter-rouge">aaa</code> would match, but <code class="language-plaintext highlighter-rouge">aa</code> would not</li>
      <li>Note: if you have a string of <code class="language-plaintext highlighter-rouge">aaaa</code> this would match both the first 3 <code class="language-plaintext highlighter-rouge">a</code> characters, and the last 3 <code class="language-plaintext highlighter-rouge">a</code> characters so you may need to consider using boundaries or anchors to ensure you match the correct text
        <ul>
          <li>For example <code class="language-plaintext highlighter-rouge">a{3}}\b</code> would match the last 3 <code class="language-plaintext highlighter-rouge">a</code> characters in <code class="language-plaintext highlighter-rouge">aaaa</code>, but not the first 3 as it requires a word boundary after the 3rd <code class="language-plaintext highlighter-rouge">a</code></li>
        </ul>
      </li>
    </ul>
  </li>
  <li>A regex of <code class="language-plaintext highlighter-rouge">a{3,}</code> would match 3 or more consecutive <code class="language-plaintext highlighter-rouge">a</code> characters
    <ul>
      <li>E.g. <code class="language-plaintext highlighter-rouge">aaa</code> and <code class="language-plaintext highlighter-rouge">aaaa</code> would match, but <code class="language-plaintext highlighter-rouge">aa</code> would not</li>
    </ul>
  </li>
  <li>A regex of <code class="language-plaintext highlighter-rouge">a{3,5}</code> would match between 3 and 5 consecutive <code class="language-plaintext highlighter-rouge">a</code> characters
    <ul>
      <li>E.g. <code class="language-plaintext highlighter-rouge">aaa</code>, <code class="language-plaintext highlighter-rouge">aaaa</code>, and <code class="language-plaintext highlighter-rouge">aaaaa</code> would match</li>
      <li>Again, similar to the first example, while <code class="language-plaintext highlighter-rouge">aaaaaa</code> would not match intentionally you may need to consider using boundaries or anchors to ensure you match the correct text</li>
    </ul>
  </li>
  <li>A regex of <code class="language-plaintext highlighter-rouge">[a-c]{2}</code> would match 2 consecutive characters that are either <code class="language-plaintext highlighter-rouge">a</code>, <code class="language-plaintext highlighter-rouge">b</code>, or <code class="language-plaintext highlighter-rouge">c</code>
    <ul>
      <li>E.g. <code class="language-plaintext highlighter-rouge">aa</code>, <code class="language-plaintext highlighter-rouge">ab</code>, <code class="language-plaintext highlighter-rouge">ac</code>, <code class="language-plaintext highlighter-rouge">ba</code>, <code class="language-plaintext highlighter-rouge">bb</code>, <code class="language-plaintext highlighter-rouge">bc</code>, <code class="language-plaintext highlighter-rouge">ca</code>, <code class="language-plaintext highlighter-rouge">cb</code>, and <code class="language-plaintext highlighter-rouge">cc</code> would all match</li>
    </ul>
  </li>
  <li>A regex of <code class="language-plaintext highlighter-rouge">[0-9]{4}</code> would match 4 consecutive numerical digits
    <ul>
      <li>E.g. <code class="language-plaintext highlighter-rouge">1234</code> would match, but <code class="language-plaintext highlighter-rouge">123</code> would not</li>
      <li>This could also be written <code class="language-plaintext highlighter-rouge">\d{4}</code> as <code class="language-plaintext highlighter-rouge">\d</code> is a shorthand for <code class="language-plaintext highlighter-rouge">[0-9]</code>
        <ul>
          <li>Be cautious, as mentioned before, some tools may not support the <code class="language-plaintext highlighter-rouge">\d</code> character</li>
          <li>If you need to be more specific with the digits match, for example 1-5, you could use <code class="language-plaintext highlighter-rouge">[1-5]{4}</code> but <code class="language-plaintext highlighter-rouge">\d{4}</code> would match any digit</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p><a href="#top">Back to top</a></p>

<h3 id="a-simple-real-world-example">A simple real world example</h3>

<p>When I worked for Vocera one of my roles was integrating 3rd party systems, such as Nurse Call systems, with our platform. In part, this involved processing incoming text based data and sample sections of the received data to then be stored in the database. The data was received as a string of text, and the data we needed to extract was in a specific format. For example, we might receive a string of text like this:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ICU Room 101 Nurse
</code></pre></div></div>

<p>We might need to extract the room details, such as <code class="language-plaintext highlighter-rouge">Room 101</code> but we wouldn’t know the room number ahead of time. So we might use a regex such as <code class="language-plaintext highlighter-rouge">Room\s\d{3,}</code>. This would match as follows:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Room</code> - matches the literal text “<strong>Room</strong>”</li>
  <li><code class="language-plaintext highlighter-rouge">\s</code> - matches the space character between <strong>Room</strong> and the room number</li>
  <li><code class="language-plaintext highlighter-rouge">\d{3,}</code> - matches 3 or more consecutive numerical digits which would be the room number
    <ul>
      <li><code class="language-plaintext highlighter-rouge">\d</code> is a shorthand for <code class="language-plaintext highlighter-rouge">[0-9]</code> so this would match any digit between 0 and 9</li>
      <li><code class="language-plaintext highlighter-rouge">{3,}</code> means 3 or more of the preceding character, so this would match 3 or more consecutive digits such as “<strong>101</strong>” or “<strong>1234</strong>”</li>
    </ul>
  </li>
</ul>

<p>With this regex we could match incoming data such as:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ICU Room 101 Nurse

or

ED Room 1234 Toilet
</code></pre></div></div>

<p><a href="#top">Back to top</a></p>

<h2 id="capture-groups-and-back-references">Capture groups and back references</h2>

<p>Capture groups are a way of capturing a specific part of a match which can then be referenced either later in the same regex, a back reference, or as part of reformatting the string.</p>

<h3 id="capture-groups">Capture groups</h3>

<p>Capture groups are defined by wrapping the part of the regex you want to capture in <code class="language-plaintext highlighter-rouge">(</code> and <code class="language-plaintext highlighter-rouge">)</code>. For example, if we wanted to capture the room number from the example above we could use the regex <code class="language-plaintext highlighter-rouge">Room\s(\d{3,})</code>. This would match the following:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Room</code> - matches the literal text <em>Room</em></li>
  <li><code class="language-plaintext highlighter-rouge">\s</code> - matches the space character between <em>Room</em> and the room number</li>
  <li><code class="language-plaintext highlighter-rouge">(</code> - start of the capture group
    <ul>
      <li><code class="language-plaintext highlighter-rouge">\d{3,}</code> - matches 3 or more consecutive numerical digits which would be the room number</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">)</code> - end of the capture group</li>
</ul>

<p>The capture group would then contain specifically just the room number, such as <em>101</em> from “ICU Room 101 Nurse”</p>

<p><a href="#top">Back to top</a></p>

<h3 id="back-references">Back references</h3>

<p>Back references allow us to reference the content of a capture group later in the same regex. Imagine a simple example where we want to match a word where the first letter and the last letter are the same, such as <em>wow</em> or <em>dad</em>. We could use the regex <code class="language-plaintext highlighter-rouge">(\w)\w\1</code> to match this. This would match as follows:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">(</code> - start of the capture group
    <ul>
      <li><code class="language-plaintext highlighter-rouge">\w</code> - matches any alphanumeric character</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">)</code> - end of the capture group</li>
  <li><code class="language-plaintext highlighter-rouge">\w</code> - matches any alphanumeric character - not being captured</li>
  <li><code class="language-plaintext highlighter-rouge">\1</code> - matches the contents of the first capture group</li>
</ul>

<p>For the example of “wow” this would match as follows:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">(</code> - start of the capture group
    <ul>
      <li><code class="language-plaintext highlighter-rouge">\w</code> - matches the first “<strong>w</strong>” character</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">)</code> - end of the capture group</li>
  <li><code class="language-plaintext highlighter-rouge">\w</code> - matches the “<strong>o</strong>” character - not being captured</li>
  <li><code class="language-plaintext highlighter-rouge">\1</code> - matches the contents of the first capture group, which is “<strong>w</strong>” from the first capture group</li>
</ul>

<p><a href="#top">Back to top</a></p>

<h3 id="capture-groups-and-reformatting">Capture groups and reformatting</h3>

<p>Another example from my days at Vocera. We would often receive incoming data such as <code class="language-plaintext highlighter-rouge">Patient Critical - D405 - 1</code>. To familiarise you “Patient Critical” would be a patient’s status, so it could be “Patient Critical” or “Needs Toilet” or any other range of statuses as defined by the 3rd party systems.  The “D405” would be a room number, and the “1” would be a bed number within the room. We would need to reformat the message starting with with room number, then the bed number before finally including the status as in this format care staff such as nurses would already be able to start heading towards a given room before they’re finished listening to the message.</p>

<p>We would use a regex such as <code class="language-plaintext highlighter-rouge">(.*?)\s-\s(\w\d+)\s-\s(\d)</code> to match the incoming data. This would match as follows:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">(</code> - start of the first capture group
    <ul>
      <li><code class="language-plaintext highlighter-rouge">.*?</code> - matches any character, zero or more times, but as few as possible
        <ul>
          <li>This would match the status, such as “<strong>Patient Critical</strong>” or “<strong>Needs Toilet</strong>”</li>
          <li>The <code class="language-plaintext highlighter-rouge">?</code> makes the match lazy, so it will match as few characters as possible before matching the next part of the regex</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">)</code> - end of the first capture group</li>
  <li><code class="language-plaintext highlighter-rouge">\s-\s</code> - matches the literal text “ - “ with a space either side</li>
  <li><code class="language-plaintext highlighter-rouge">(</code> - start of the second capture group
    <ul>
      <li><code class="language-plaintext highlighter-rouge">\w</code> - matches any alphanumeric character, one or more times
        <ul>
          <li>This would match the room number, such as “<strong>D</strong>”</li>
          <li>The <code class="language-plaintext highlighter-rouge">\w</code> is a shorthand for <code class="language-plaintext highlighter-rouge">[a-zA-Z0-9_]</code> which means any alphanumeric character or underscore</li>
        </ul>
      </li>
      <li><code class="language-plaintext highlighter-rouge">\d+</code> - matches any numerical digit, one or more times
        <ul>
          <li>This would match the bed number, such as “<strong>405</strong>”</li>
          <li>The <code class="language-plaintext highlighter-rouge">+</code> makes the match greedy, so it will match as many characters as possible before matching the next part of the regex</li>
          <li>The <code class="language-plaintext highlighter-rouge">+</code> is a shorthand for <code class="language-plaintext highlighter-rouge">{1,}</code> which means one or more of the preceding character</li>
          <li>The <code class="language-plaintext highlighter-rouge">\d</code> is a shorthand for <code class="language-plaintext highlighter-rouge">[0-9]</code> which means any numerical digit</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>The <code class="language-plaintext highlighter-rouge">)</code> - end of the second capture group</li>
  <li><code class="language-plaintext highlighter-rouge">\s-\s</code> - matches the literal text “ - “ with a space either side</li>
  <li><code class="language-plaintext highlighter-rouge">(</code> - start of the third capture group
    <ul>
      <li><code class="language-plaintext highlighter-rouge">\d</code> - matches any numerical digit
        <ul>
          <li>This would match the bed number, such as “<strong>1</strong>”</li>
          <li>The <code class="language-plaintext highlighter-rouge">\d</code> is a shorthand for <code class="language-plaintext highlighter-rouge">[0-9]</code> which means any numerical digit</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">)</code> - end of the third capture group</li>
</ul>

<p>With this regex we would have 3 capture groups that our software could then continue to process.  We might then have a reformatting string such as this: <code class="language-plaintext highlighter-rouge">Room $2 Bed $3 Status: $1</code>. This would reformat the incoming data to be “<strong>Room D405 Bed 1 Status: Patient Critical</strong>”.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="context">Context</h2>

<p>Particularly when working with log files being able to use a regex to search for specific text but then gather log events that happened immediately before or after the match can be extremely useful. This is where context comes in.</p>

<p>In <code class="language-plaintext highlighter-rouge">grep</code> we have the switches <code class="language-plaintext highlighter-rouge">-A</code>, <code class="language-plaintext highlighter-rouge">-B</code>, and <code class="language-plaintext highlighter-rouge">-C</code> for example where <code class="language-plaintext highlighter-rouge">-A</code> means “after”, <code class="language-plaintext highlighter-rouge">-B</code> means “before”, and <code class="language-plaintext highlighter-rouge">-C</code> means “context”. Similarly PowerShell provides the <code class="language-plaintext highlighter-rouge">-Context</code> switch which takes a comma separated list of numbers to specify the number of lines before and after the match to include in the output, such as <code class="language-plaintext highlighter-rouge">-Context 1,2</code> to include 1 line before and 2 lines after the match or <code class="language-plaintext highlighter-rouge">-Context 2</code> to include 2 lines before and 2 lines after the match.</p>

<p><a href="#top">Back to top</a></p>

<h2 id="getting-some-practice">Getting Some Practice</h2>

<p>I’ve prepared some practice materials, available from my GitHub account here: <a href="https://github.com/GingerGraham/DemosAndPracticeFiles/tree/main/RegularExpressions">https://github.com/GingerGraham/DemosAndPracticeFiles/tree/main/RegularExpressions</a></p>

<p>The materials include some simple sample text and log files and a <a href="https://github.com/GingerGraham/DemosAndPracticeFiles/blob/main/RegularExpressions/DemosAndExamples.md">tutorial</a> to walk you through some examples.</p>

<h2 id="useful-resources">Useful resources</h2>

<ul>
  <li><a href="https://www.regex101.com/">https://www.regex101.com/</a></li>
  <li><a href="http://www.rubular.com/">http://www.rubular.com/</a></li>
  <li><a href="https://www.regular-expressions.info/tutorial.html">https://www.regular-expressions.info/tutorial.html</a></li>
</ul>

<p><a href="#top">Back to top</a></p>

<p>If this article helped inspire you please consider sharing this article with your friends and colleagues, or let me know via <a href="https://www.linkedin.com/in/wattsgraham/">LinkedIn</a> or <a href="https://twitter.com/Ginger_Graham">X / Twitter</a>. If you have any ideas for further content you might like to see please let me know too.</p>

<p><a href="#top">Back to top</a></p>]]></content><author><name>Graham Watts</name><email>mail@grahamwatts.co.uk</email></author><category term="blog" /><category term="devops" /><category term="development" /><category term="technical" /><summary type="html"><![CDATA[Regular expressions are a powerful tool for searching and manipulating text. They are used in many programming languages, text editors, and other tools. They are also a source of confusion and frustration for many people. My aim with this article is to demystify regular expressions and help you understand how to use them by introducing the concepts and terminology used, demonstrate some examples, and then point you to some resources for further learning.]]></summary></entry></feed>