13 Jan 2023To search and replace with zsh
, there are two ways.
With ${var//XXX/YYY}
This will replace all occurences of XXX
in $var
with YYY
.
Note that you can interpolate variables inside of XXX
, so ${var//${input}/YYY}
with input="foo"
will replace foo
with YYY
in $var
.
You can use only one /
instead of //
to only replace the first occurence.
With ${var:gs/XXX/YYY}
The :s/XXX/YYY
modifier is the basic syntax to replace XXX
with YYY
.
It does not allow for interpolating variables. You need to replace s
with gs
to make it a global search and replace.
The only advantage over the other syntax in my opinion is that you can swap the delimiter character (/
) with any other character you like. So if your patterns are heavy on /
, you can swap them for _
for example for a more readable format, like ${var:gs_/_#}
to replace all /
with #
.
12 Jan 2023Sometimes, you need to download a whole website locally. And for that, HTTrack is the best tool I ever found.
Sure, you could use wget
and some recursion to get what you want, but HTTrack solves all that for you.
Reasons you might need to download a whole website: - You need to browse it when you don't have an internet connection (on the go, with low data usage, in a train, etc) - You want to run scripts on the content, and it's faster with a local copy than a remote one - You want to make a copy of a website that might disappear
03 Jan 2023By default in zsh, if you define a local myVariable
it will be available to the whole script running it.
Any of those variables defined in your .zshrc
will also be visible in your terminal. This can create weird bugs when you accidentally defined a variable with the same name as another zsh script.
Note that those are not environment variables. Even if you can read them from your zsh terminal, they are not accessible from other tools. To explicitly make them available as environment variables, you need to export
them.
If you define a variable inside a function, it stays scoped to that function, though. Also, anonymous functions are run as soon as they are defined, and discarded afterwards.
Using those two features, we can define our variables without them being available in the terminal global scope.
I find it a best practice to wrap any of my sourced zsh scripts in a function () { }
block, like this:
local one=1
function () {
local two=2
export THREE=3
}
one
is accessible in the whole .zsh
script, including inside of the function, and even during your whole terminal session (you can echo $one
). Other, non-zsh, scripts won't be able to read it though.
two
is accessible only in the body of the function, and is scoped there and won't be accessible from outside. It's perfect for small variables you need to simplify your code but don't need laying around.
THREE
is an environment variable, that can be used in the terminal (echo $THREE
) as well as in any other script you're running from the terminal. It's useful if you need to set some global flags or editing global settings (like the $PATH
variable).
02 Jan 2023Imagine the following scenario:
local projects=(blog www meetups)
local color_project_blog=146
local color_project_www=75
local color_project_meetups=23
And now you'd like to iterate on all projects, and display their associated color. You need dynamic variables, where part of the variable name is itself coming from another variable.
This will be achieved using two zsh modifiers: ${(P)}
and ${:-}
.
Building a variable name using dynamic variables
local name=${real_name:-Alice}
means: set the $name
variable to the content of the $real_name
variable. If $real_name
is empty, use Alice
as the default value.
Anything after the :-
is used as the default value, and it can even interpolate variables. So if you also have local default_name="Alice"
, then you can have local name=${real_name:-Default name set to $default_name}
.
That way, $name
is equal to $real_name
, unless $real_name
is empty, in which case it's set to Default name is Alice
.
One can even remove the first variable to force zsh to use the default value. So local name=${:-Default name is $default_name}
is valid syntax and will set $name
to Default name is Alice
, allowing one to interpolate variable names when setting variables.
Reading such a dynamic variable
We now have a fancy way of building a string with variable interpolation inside a ${}
.
To read it, it's a bit more complex as we need to use this (already barbaric-looking) syntax with another modifier.
local projects=(blog www meetups)
local color_project_blog=146
local color_project_www=75
local color_project_meetups=23
for project_name in $projects; do
local project_color=${(P)${:-color_project_${project_name}}}
echo "${project_name} color is ${project_color}"
done
01 Jan 2023When writing zsh scripts, I often needs to iterate on elements, but depending on how I create them, they can either be a core zsh array, a string of words, or the output of a command, delimited by newlines.
Iterating on an array
Iterating on arrays in zsh has a pretty straightforward syntax:
local projects=(firost aberlaas golgoth);
for project in $projects; do
echo "${project} is one of my projects";
done
Handling string of words
By default, zsh does not split a string in words like other shells (Bash) do, so iterating on words requires the ${=}
syntax.
The ${=}
notation triggers the Bash-compatible behavior by switching the SH_WORD_SPLIT
zsh option for that variable.
Iterating on the words
local projects="firost aberlaas golgoth";
for project in ${=projects}; do
echo "${project} is one of my projects";
done
Accessing one element specifically
Note that if I want to convert the string of words into an array, to specifically access one of its elements, I need to wrap it in ()
.
local projects="firost aberlaas golgoth";
local projectsArray=(${=projects})
echo "$projectsArray[2] is my second project"
A note on words
Note that zsh will split by words, meaning that multiple spaces will be removed:
local projects=" firost aberlaas golgoth ";
local projectsArray=(${=projects})
echo "$projectsArray[2] is still my second project"
Iterating on lines in a string
New lines are considered words separators, so technically the ${=}
could be used to split a string by newlines. But this will also split by spaces, so if your lines contain spaces, you final array will not be what you expect.
To split only by new lines and not by space, you need to use the ${(f)}
syntax. This proves useful when parsing long output from other commands
Iterating on the lines
local projects="$(ls)";
for project in ${(f)projects}; do
echo "${project} is a project file in my dir";
done
Accessing one line specifically
local projects="$(ls)";
local projectsArray=(${(f)projects})
echo "$projectsArray[2] is my second project"