shell script – Hackaday https://hackaday.com Fresh hacks every day Thu, 12 Feb 2026 07:41:40 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 156670177 Bash via Transpiler https://hackaday.com/2026/02/12/bash-via-transpiler/ https://hackaday.com/2026/02/12/bash-via-transpiler/#comments Thu, 12 Feb 2026 16:30:22 +0000 https://hackaday.com/?p=915129 It is no secret that we often use and abuse bash to write things that ought to be in a different language. But bash does have its attractions. In the …read more]]>

It is no secret that we often use and abuse bash to write things that ought to be in a different language. But bash does have its attractions. In the modern world, it is practically everywhere. It can also be very expressive, but perhaps hard to read.

We’ve talked about Amber before, a language that is made to be easier to read and write, but transpiles to bash so it can run anywhere. The FOSDEM 2026 conference featured a paper by [Daniele Scasciafratte] that shows how to best use Amber. If you prefer slides to a video, you can read a copy of the presentation.

For an example, here’s a typical Amber script. It compiles fully to a somewhat longer bash script:


import * from "std/env"
fun example(value:Num = 1) {
   if 1 > 0 {
      let numbers = [value, value]
      let sum = 0
      loop i in numbers {
         sum += numbers[i]
    }
    echo "it's " + "me"
    return sum
   }
   fail 1
}

echo example(1) failed {
   echo "What???"
   is_command("echo")
}

The slides have even more examples. The language seems somewhat Python-like, and you can easily figure out most of it from reading the examples. While bash is nearly universal, the programs a script might use may not be. If you have it, the Amber code will employ bshchk to check dependencies before execution.

According to the slides, zsh support is on the way, too. Overall, it looks like it would be a great tool if you have to deploy with bash or even if you just want an easier way to script.

We’ve looked at Amber before. Besides, there are a ton of crazy things you can do with bash.

]]>
https://hackaday.com/2026/02/12/bash-via-transpiler/feed/ 13 915129 amber
Linux Fu: Yet Another Shell Script Trick https://hackaday.com/2026/01/06/linux-fu-yet-another-shell-script-trick/ https://hackaday.com/2026/01/06/linux-fu-yet-another-shell-script-trick/#comments Tue, 06 Jan 2026 18:00:55 +0000 https://hackaday.com/?p=886196 I’m going to go ahead and admit it: I really have too many tray icons. You know the ones. They sit on your taskbar, perhaps doing something in the background …read more]]>

I’m going to go ahead and admit it: I really have too many tray icons. You know the ones. They sit on your taskbar, perhaps doing something in the background or, at least, giving you fingertip access to some service. You’d think that creating a custom tray icon would be hard, but on Linux, it can be surprisingly simple. Part of the reason is that the Freedesktop people created standards, so you don’t typically have to worry about how it works on KDE vs. GNOME or any of the other desktop environments. That’s a big win.

In fact, it is simple enough that you can even make your own tray icons with a lowly shell script. Well, of course, like most interesting shell scripts, you need some helper programs and, in this case, we’ll use YAD — which is “yet another dialog,” a derivative of Zenity. It’s a GTK program that may cause minor issues if you primarily use KDE, but they are nothing insurmountable.

The program is somewhat of a Swiss army knife. You can use it to make dialogs, file pickers, color selectors, printer dialogs, and even — in some versions — simple web browsers. We’ve seen plenty of tools to make pretty scripts, of course. However, the ability to quickly make good-looking taskbar icons is a big win compared to many other tools.

Docs

Depending on what you want to do, YAD will read things from a command line, a file, or standard input. There are dozens of options, and it is, honestly, fairly confusing. Luckily, [Ingemar Karlsson] wrote the Yad Guide, which is very digestible and full of examples.

Exactly what you need will depend on what you want to do. In my case, I want a tray icon that picks up the latest posts from my favorite website. You know. Hackaday?

The Web Connection

YAD can render HTML using WebKit. However, I ran into immediate problems. The version in the repos for the Linux I use was too old to include the HTML option. I found a supposedly statically linked version, but it was missing dependencies. Even after I fixed that, the program still reported errors related to the NVIDIA OpenGL stack.

I quickly abandoned the idea of using a web browser. I turned to two other YAD features. First, the basic dialog can hold text and, in most cases, renders quasi-HTML because it uses the Pango library. However, there is also a text-info dialog built in. Unlike most other YAD features, the text-info dialog reads its input from standard input. However, it doesn’t render markup.

In the end, I decided to try them both. Why not? It is simple enough. But first, I needed a tray icon.

The Tray

YAD can provide a “notification,” which is what it calls a tray icon. You can specify an icon, some text, and a right-click context menu. In addition, it can react when someone clicks on the icon.

Can you find the tray icon we’re talking about?

I decided to write a script with multiple personalities. If you run it with no arguments, it sets up the tray icon. If you pass anything to it, it will show a dialog with the latest Hackaday articles from the RSS feed. I wanted to make those links clickable, and that turned out to be a bit of a wrinkle. Both versions will do the job, but they each need a different approach, as you will see.

Here’s the tray code:


yad --notification --image="$0.icon.png" --text="Hackaday Now" \
   --menu="Quit!quit!gtk-quit" --command="$0 show" --no-middle

You can probably guess at most of this without the manual. The image is stored in a file with the same name as the script, but with .icon.png at the end. That’s the icon in the tray. The simple menu provides an option to exit the program. If you click the icon, it calls the same script again, but with the “show” argument. The script doesn’t care what the argument is, but maybe one day it will.

So that part of the project was extremely simple. The next job is making the dialog appear.

Text Info

Grabbing the RSS feed with wget is trivial. You could use grep, sed, and bash pattern replacement to extract the titles and URLs, but I opted for awk and a brute-force parsing approach.

This works, but the URLs are long and not terribly attractive. The list is scrollable, and there are more links below the visible ones.

The standard output of awk pipes into YAD, but you can’t readily apply formatting or hyperlinks. You can use formatting in regular dialog text, which will appear before the other output. That’s where the yellow “Hackaday Today!” title in the adjacent screenshot gets set. In addition, you can automatically detect URLs and make them clickable using the --show-uri option.

Here’s the relevant command:


yad --text-info \
   --text "<span foreground='$TITLECOLOR'><b><big><big>Hackaday Today!</big></big></b></span>" \
   --show-uri --window-icon="$0.icon.png" \
   --uri-color=$LINKCOLOR --width=$WIDTH --height=$HEIGHT \
   --Title "Hackaday Posts" --button="Close!gtk-ok" \
   --buttons-layout=center --escape-ok 2>/dev/null

You’ll notice that the –text option does take Pango formatting and the --show-uri option makes the links clickable. By default, dialogs have an Open and Cancel button, but I forced this one to have a single close button, accept escape, and I wanted the button centered.

As you can see in the screenshot, the result isn’t bad, but it does require having the title followed by a long URL that you can click on and that’s a little ugly.

Stock Dialog

Using a standard dialog instead of text-info allows better formatting.

Since the –text option works with any dialog and handles formatting, I decided to try that. The awk code was nearly the same, except for the output formatting. In addition, the output now needs to go on the command line instead of through a pipe.

This does make the script a bit more unwieldy. The awk script sets a variable, since jamming the command into the already busy YAD command line would make the script more complicated to read and work with.

The YAD command is still simple, though:


yad \
--text="$DATA" \
--window-icon="$0.icon.png" \
--width=$WIDTH --height=$HEIGHT \
--Title "Hackaday Posts" --button="Close!gtk-ok" \
--buttons-layout=center --escape-ok

The DATA variable has the formatted output text. The result looks better, as you can see in the screenshot. In either version, if you click an underlined link, your default browser should open the relevant post.

Other Choices

If you want to install either script, you can get it from GitHub. Of course, you could do this in Python or any other conventional language. There are also programs for “minimizing” another program to the tray, like AllTray or KDocker, although some of these may only work with X11 and not Wayland.

It would have been nice to have an integrated browser, although, thanks again to FreeDesktop, it is simple enough to open a URL and launch the system’s default browser.

Prefer your Hackaday feed on the command line? Check out the comments for this post. Meanwhile, send us a tip (you know, a link to your project, not a gratuity) and maybe you’ll see your own project show up on the feed.

]]>
https://hackaday.com/2026/01/06/linux-fu-yet-another-shell-script-trick/feed/ 3 886196 LinuxFu
Tracing the #!: How the Linux Kernel Handles the Shebang https://hackaday.com/2025/04/11/tracing-the-how-the-linux-kernel-handles-the-shebang/ https://hackaday.com/2025/04/11/tracing-the-how-the-linux-kernel-handles-the-shebang/#comments Sat, 12 Apr 2025 05:00:12 +0000 https://hackaday.com/?p=771207 One of the delights in Bash, zsh, or whichever shell tickles your fancy in your OSS distribution of choice, is the ease of which you can use scripts. These can …read more]]>

One of the delights in Bash, zsh, or whichever shell tickles your fancy in your OSS distribution of choice, is the ease of which you can use scripts. These can be shell scripts, or use the Perl, Python or another interpreter, as defined by the shebang (#!) at the beginning of the script. This signature is followed by the path to the interpreter, which can be /bin/sh for maximum compatibility across OSes, but how does this actually work? As [Bruno Croci] found while digging into this question, it is not the shell that interprets the shebang, but the kernel.

It’s easy enough to find out the basic execution sequence using strace after you run an executable shell script with said shebang in place. The first point is in execve, a syscall that gets one straight into the Linux kernel (fs/exec.c). Here the ‘binary program’ is analyzed for its executable format, which for the shell script gets us to binfmt_script.c. Incidentally the binfmt_misc.c source file provides an interesting detour as it concerns magic byte sequences to do something similar as a shebang.

As a bonus [Bruno] also digs into the difference between executing a script with shebang or running it in a shell (e.g. sh script.sh), before wrapping up with a look at where the execute permission on a shebang-ed shell script is checked.

]]>
https://hackaday.com/2025/04/11/tracing-the-how-the-linux-kernel-handles-the-shebang/feed/ 9 771207 optimizing-linux-featured
A 6502, In The Shell https://hackaday.com/2025/03/17/a-6502-in-the-shell/ https://hackaday.com/2025/03/17/a-6502-in-the-shell/#comments Mon, 17 Mar 2025 08:00:00 +0000 https://hackaday.com/?p=767778 Shell scripting is an often forgotten programming environment, relegated to simple automation tasks and little else. In fact, it’s possible to achieve much more complex tasks in the shell. As …read more]]>

Shell scripting is an often forgotten programming environment, relegated to simple automation tasks and little else. In fact, it’s possible to achieve much more complex tasks in the shell. As an example, here’s [calebccf] with an emulated 6502 system in a busybox ash shell script.

What’s in the emulator? A simple 6502 system with RAM, ROM, and an emulated serial port on STDIO. It comes with the wozmon Apple 1 monitor and BASIC, making for a very mid-1970s experience. There’s even a built-in monitor and debugger, which from our memories of debugging hand-assembled 8-bit code back in the day, should be extremely useful.

Although the default machine has a generous 32k of RAM and 16k ROM, you can easily adjust these limits by editing machine.sh. In addition, you can get a log of execution via a socket if you like. Don’t expect it to run too fast, and we did have to adjust the #! line to get it to run on our system (we pointed it to bash, but your results may vary).

What you use this for is up to you, but we’re sure you’ll all agree it’s an impressive feat in the shell. It’s not the first time we’ve seen some impressive feats there, though. Our Linux Fu column does a lot with the shell if you want further inspiration.

]]>
https://hackaday.com/2025/03/17/a-6502-in-the-shell/feed/ 8 767778 fake-6502-featured
Linux Fu: Use the Source (Command), Luke https://hackaday.com/2025/03/13/linux-fu-use-the-source-command-luke/ https://hackaday.com/2025/03/13/linux-fu-use-the-source-command-luke/#comments Thu, 13 Mar 2025 17:00:48 +0000 https://hackaday.com/?p=766621 You can argue if bash is a good programming language or not, but you can’t argue that it is a programming language. However, there are a few oddities about it …read more]]>

You can argue if bash is a good programming language or not, but you can’t argue that it is a programming language. However, there are a few oddities about it that make it different from most other languages you probably know. For one thing, variables are dynamically scoped. Second, you can easily change variables in an upper scope. This leads to a problem when you want to do something like reset your path:

#!/bin/bash
#: This does NOT work
PATH=/usr/bin:/bin

Well, actually, it does work; it just doesn’t work the way you imagine it might. The key is to realize that when you execute our script (say, resetpath), a new copy of bash runs. It inherits all the variables from your shell. Now the script sets PATH for the new copy of bash. Anything else you run in that script will see your change. But when the script exits, the new copy of bash is gone and the old copy sees the same old PATH it always did.

Sometimes, this is a benefit, similar to “call by value” in other languages. However, what if you want to influence things? What’s more is that the situation is just the opposite within bash functions. For example:

#!/bin/bash

b() {
  echo B: $x
  x=200
}

a() {
  x=100
  b
  echo A: $x
}

a
#: output
#: B: 100
#: A: 200

Function b has no difficulty reading and even setting variable x.

The Answer, Of Source Course

The answer to the first problem is to use the source command (which can be either the word source or a single period). This tells bash to avoid running a new interpreter and just pretend you’d entered all the lines in a file from the console.

This is great sometimes. Our resetpath script will actually work just fine with either of these commands:

source resetpath
. resetpath

You don’t even need the #! line, although it doesn’t hurt. However, there are a few problems.

The Catches

First, if you exit, then you exit the entire shell, not something you probably meant to do. Second, you wind up polluting the variable space of the parent. For example, if your script creates a function X, with a regular shell script, that function goes away as soon as your script stops. With a source script, function X now will live forever unless you do something about it.

Neither of these problems are insurmountable, of course, and you’ll see a few ways to address it in the example code in this post.

A Simple Example

If you spend a lot of time on the command line, you might want to have shortcut names for directories. What’s more, you might want to execute a little script when you go to particular directories or even when you leave them.

My plan is to keep a simple file in ~/.proj_dirs. To keep things simple, I’m assuming you can figure out the bash format:

PROJ_DIRS["docs"]="~/library/documents"
PROJ_DIRS["video"]="~/library/videos"
PROJ_DIRS["arduino"]="/home/alw/projects/embedded/Arduino"
. . .

The eventual goal is to replace the cd command (or, at least, allow for that). However, it would be a pain to have to write something like “source pcd arduino” every time.

The Alias Solution

The answer is pretty simple. You can create a script that can install itself as an alias. Here’s the basic flow:

#!/bin/bash
#: This is not a bash shell script 
#: But needs to be sourced. However... 
#: Try: 
#: eval $(__project_dir.sh --__install project_dir) 
if [ "$1" == "--__install" ] # this should only be called from "real" script 
then 
   aname="$2" 
   if [ "$aname" == "" ] 
   then 
      aname="pcd" 
    fi
    echo -n "alias '$aname'='source " 
    aname=$(realpath -s "$0")
    echo "$aname'" 
    exit 0 
fi 
#: Your source script goes here
...

The idea is that if you run as a regular script with –__install, it returns the alias command. You can then eval that in, for example, a startup script (like .bashrc or .profile), and then you’ll have the alias you want. By default, the code uses pcd, although you can set up any name you like on the command line. You could even create an alias for cd if you wanted to do that.

Why Not Automatic?

You could, of course, detect if you were running normally or as a source automatically. Turns out this is somewhat finicky across shells, although if you are sure you are always using real bash, it is feasible. For example:

if [[ "${BASH_SOURCE[0]}" == "$0" ]]
then
   echo I am not sourced!
fi

Variables

Once you have the basic framework, it is easy to write the scripts to read the “database” (also using source) and do the actual work. However, there is a slight problem. Once you produce all the variables you need to do the work, it leaves all that pollution in your shell’s namespace.

Of course, you could write a function to clean up everything you use, but that’s a pain and error prone, too. A better idea is to write your code in a bash function. Then you can use local variables that will go away when the function returns. That leaves you with just your function to clear up with unset.

That leads to this simple framework:

#!/bin/bash
if [[ "${BASH_SOURCE[0]}" == "$0" ]]
then
   if [ "$1" == "--__install" ] # this should only be called from "real" script
   then
      aname="$2"
      if [ "$aname" == "" ]
      then
        aname="pcd" # default alias name
      fi
      echo -n "alias '$aname'='source "
      aname=$(realpath -s "$0")
      echo "$aname'"
      exit 0
   fi
   echo "You must source this script"
   exit 1
fi

#: Ok your script goes here

main() {
. . .

}

#: Be sure to have this at the end
#: Actually named with underscores in the real code
#: But that upsets the rendering in browser
#: Actual code at https://gist.github.com/wd5gnr/c5681f2f7072938d5d7afe7a1e3e9132
go() {
   local tmprv
   main "$@"
   tmprv=$?
   unset main, go
   return $tmprv
}

go "$@"
return $?

The very bottom calls the go function, which calls your main function. Then the go function destroys your main function and itself. If you create new functions that you don’t want to keep around, you’ll need to destroy them yourself. Besides, you might be creating functions you want to keep, so the framework can’t decide.

The Whole Thing

You can find the entire example on GitHub. Outside of the management of the alias and the variable scope, the script is unremarkable. Note the optional scripts in the directories (.dir_enter and .dir_exit) are sourced also, so they only need to be readable (-r) not executable (-x).

The only other nuance is that if you enter anything as a directory that the program doesn’t recognize, it assumes it is an actual directory, so you can use this to replace the cd command entirely if you want.

Since the script can tell if it is sourced or not, it is possible to start in the source mode and then call yourself as a normal script to do work where that makes more sense. As usual with bash, there are lots of possibilities.

We talk about bash programming a lot around here. Debugging can be helpful, although they haven’t packaged the debugger for newer versions of bash lately.

]]>
https://hackaday.com/2025/03/13/linux-fu-use-the-source-command-luke/feed/ 12 766621 LinuxFu
Getting Linux Process List Without Forking Using Just a Bash Script https://hackaday.com/2024/07/29/getting-linux-process-list-without-forking-using-just-a-bash-script/ https://hackaday.com/2024/07/29/getting-linux-process-list-without-forking-using-just-a-bash-script/#comments Mon, 29 Jul 2024 23:00:10 +0000 https://hackaday.com/?p=698388 The ps command is extremely useful when you want to get some quick information on active system processes (hence the name), especially followed by piping it into grep and kin …read more]]>

The ps command is extremely useful when you want to get some quick information on active system processes (hence the name), especially followed by piping it into grep and kin for some filtering. One gotcha is of course that ps doesn’t run in the current shell process, but is forked off into its own process, so what if everything goes wrong and you absolutely need to run ps aux on a system that is completely and utterly out of fresh process IDs to hand out? In that scenario, you fortunately can write a shell script that does the same, but all within the same shell, as [Isabella Bosia] did, with a Bash shell script.

The how and why is mostly covered in the shell script itself, using detailed comments. Initially the hope was to just read out and parse the contents of /proc/<pid>/status, but that doesn’t have details like CPU%. The result is a bit more parsing to get the desired result, as well as a significant amount of cussing in the comments. Even if it’s not entirely practical, as the odds of ending up on a system with zero free PIDs are probably between zero and NaN, but as an ‘entertaining’ job interview question and example of all the fun things one can do with shell scripting it’s definitely highly recommended.

]]>
https://hackaday.com/2024/07/29/getting-linux-process-list-without-forking-using-just-a-bash-script/feed/ 16 698388 tree-fork-featured
Linux Fu: Mixing Bash and Python https://hackaday.com/2021/05/04/linux-fu-mixing-bash-and-python/ https://hackaday.com/2021/05/04/linux-fu-mixing-bash-and-python/#comments Tue, 04 May 2021 17:01:59 +0000 https://hackaday.com/?p=473327 Although bash scripts are regularly maligned, they do have a certain simplicity and ease of creation that makes them hard to resist. But sometimes you really need to do some …read more]]>

Although bash scripts are regularly maligned, they do have a certain simplicity and ease of creation that makes them hard to resist. But sometimes you really need to do some heavy lifting in another language. I’ll talk about Python, but actually, you can use many different languages with this technique, although you might need a little adaptation, depending on your language of choice.

Of course, you don’t have to do anything special to call another program from a bash script. After all, that’s what it’s mainly used for: calling other programs. However, it isn’t very handy to have your script spread out over multiple files. They can get out of sync and if you want to send it to someone or another machine, you have to remember what to get. It is nicer to have everything in one file.

The Documents are All Here

The key is to use the often forgotten here document feature of bash. This works best when the program in question is an interpreter like Python.


#!/bin/bash
echo Welcome to our shell script

python <<__EOF_PYTHON_SCRIPT
print 'Howdy from Python!'
__EOF_PYTHON_SCRIPT

echo "And we are back!"

Variations on a Theme

Of course, any shell that supports here documents can do this, not just bash. You can use other interpreters, too. For example:

#!/bin/bash
echo Welcome to our shell script

perl <<__EOF_PERL_SCRIPT
print "Howdy from Perl\n"
__EOF_PERL_SCRIPT

echo "And we are back!"

It might be confusing, but you could even have some sections in Python and others in Perl or another language. Try not to get carried away.

Just in Case

There’s the rare case where an interpreter doesn’t take the interpreted file from the standard input. Awk is a notorious offender. While you can embed the entire script in the command line, that can be awkward and leads to quoting hassles. But it seems silly to write out a temporary file just to send to awk. Luckily, you don’t have to.

There are at least two common ways to handle this problem. The first is to use the bash process substitution. This basically creates a temporary file from a subshell’s standard output:

#!/bin/bash
# Hybrid bash/awk Word count program -- totally unnecessary, of course...

echo Counting words for "$@"
awk -f <( cat - <<EOF_AWK
    
    BEGIN {
        wcount=0;
        lcount=0;
    }
    
    {
        lcount++;
        wcount+=NF;
    }
    
    END {
        print "Lines=" lcount;
        print "Words=" wcount;
    }
    
    EOF_AWK
) "$@"

echo Hope that was enough
exit 0

Yet Another Way

There is another way to organize your process substitutions, so they are all gathered together at the end of the script surrounded by a marker such as “AWK_START” and “AWK_END” or any other pair of strings you like. The idea is to put each pseudo file in its own section at the end of the script. You can then use any number of techniques like sed or awk to strip those lines out and process substitute them like before.

There are two minor problems. First, the script needs to exit before the fake files start. That’s easy. You just have to make sure to code an exit at the end of the script, which you probably ought to do anyway. The other problem is searching for the marker text. If you search the file for, say, AWK_START, you need to make sure the search pattern itself isn’t found. You can fix this by using some arbitrary brackets in the search string or breaking up the search string. Consider this:

#!/bin/bash
# Hybrid bash/awk Word count program -- totally unnecessary, of course...

echo Counting words for "$@"
# use brackets
#awk -f <( sed -e '/[A]WK_START/,/[A]WK_END/!d' $0 ) "$@"
# or
AWK_PREFIX=AWK
awk -f <( sed -e "/${AWK_PREFIX}_START/,/${AWK_PREFIX}_END/!d" $0 ) "$@"

echo Hope that was enough
exit 0

# everything below here will be the supporting "files", in this case, just one for awk

# AWK_START

BEGIN {
    wcount=0;
    lcount=0;
}

{
    lcount++;
    wcount+=NF;
}

END {
    print "Lines=" lcount;
    print "Words=" wcount;
}

#AWK_END

There is no reason you could not have multiple fake files at the end, each with a different pair of markers. Do note, though, that the markers are sent to the program which is why they appear as comments. If these were going to a program that didn’t use # as a comment marker, you’d need to change the marker lines a bit, write a more complex sed expression, or add some commands to take off the first and last lines before sending it.

That’s a Wrap

You could argue that you can do all you need to do in one language and that’s almost certainly true. But having some tricks to embed multiple files inside a file can make creating and distributing scripts easier. This is somewhat similar to how we made self-installing archive files in a previous installment of Linux Fu. If you’d rather script in C or C++, you can do that too.

]]>
https://hackaday.com/2021/05/04/linux-fu-mixing-bash-and-python/feed/ 24 473327 LinuxFu