DEV Community

Cover image for How to customize your PS1 in Mac/Linux with emojis and animations.
Bharath Raja
Bharath Raja

Posted on

How to customize your PS1 in Mac/Linux with emojis and animations.

I recently bought a new Macbook Pro 13" and started setting it up for my development. The list went like Chrome, iTerm2, Brew, OhMyZsh, VS Code, and so on (it deserves its own post). This was my first time with OhMyZsh and I was playing around with the themes and I noticed that the PS1 was simple enough to edit. I thought it would be fun to add colourful emojis to my prompt. There started a journey of hacking for 3 daysโ€ฆ

TLDR; ๐Ÿ“ฆ Go to Repository

Bash approach

Adding a random emoji was straightforward. Then I thought of having a list of emojis that I can randomly show. Itโ€™s always nice to have variety ๐Ÿคท๐Ÿปโ€โ™‚๏ธ

$ arr=(๐Ÿ‘พ ๐Ÿ’ป ๐Ÿ€ ๐Ÿฆฎ โ›ฐ๏ธ ๐Ÿบ ๐ŸŽจ ๐Ÿƒ๐Ÿปโ€โ™‚๏ธ ๐Ÿ‘จ๐Ÿปโ€๐ŸŒพ ๐Ÿข ๐Ÿผ ๐Ÿ™ ๐Ÿณ ๐Ÿ“ ๐Ÿชต ๐Ÿ„ ๐Ÿ”ฅ ๐Ÿ ๐Ÿš ๐ŸŒŠ ๐Ÿ‰ ๐Ÿฅ ๐Ÿ‹)
$ PS1="\${arr[RANDOM%${#arr[@]} + 1]} $PS1"
๐Ÿ‰ $ echo "Noice!!!"
๐ŸŒŠ $
๐Ÿณ $
Enter fullscreen mode Exit fullscreen mode

It was so beautiful and my mind was flowing with ideas. I wanted to show time-appropriate emojis. I started simple, show food and sleep timing, else randomize from the list.

function _emoji() {
  time=$(date +%H%M)
  if (( $time > 2230 )); then
    echo -n ๐Ÿฅฑ
  elif (( $time > 2100 )); then
    echo -n ๐Ÿ•
  # ...
  # Other food timings
  elif (( $time < 500 )); then
    echo -n ๐Ÿ›Œ
    echo -n ${arr[RANDOM%${#arr[@]} + 1]}
  return 0
Enter fullscreen mode Exit fullscreen mode

It was a great start, but I wanted more. I wanted to be able to override the schedule based emoji when the activity is over (like I donโ€™t wanna see food emoji after Iโ€™ve had my lunch) at the same time I wanted some of them to be unavoidable (like I should only see sleep emoji from 2300 to 500). And it wasnโ€™t easy to maintain such a complex data structure in bash, so I moved to a language Iโ€™m very comfortable with โ€” JS.

Introducing JavaScript

Once I found a way to use JS to output to my PS1, I had much more control over what I wanted to show and when. Soon, I had a very detailed schedule that defaults to a list when empty. The default list also grew 3 folds.

const fun_list = "๐Ÿ‘ป,๐Ÿ‘พ,๐ŸŽƒ,๐Ÿ’‹,๐Ÿ‘ ,๐Ÿฅท ,๐Ÿงถ,๐Ÿงต,๐Ÿ‘‘,๐Ÿฐ,๐ŸฆŠ,๐Ÿผ,๐Ÿจ,๐Ÿท,๐Ÿธ,๐Ÿฆ‹,๐ŸŒ,๐Ÿข,๐Ÿ™,๐Ÿฆ€,๐Ÿก,๐Ÿ ,๐Ÿณ,๐Ÿฟ ,๐Ÿฆข,๐Ÿชต ,๐ŸŒต,๐Ÿ€,๐Ÿ,๐Ÿ„,๐ŸŒธ,๐ŸŒผ,๐ŸŒ,๐Ÿ”ฅ,โ˜‚๏ธ ,๐ŸŒŠ,โ„๏ธ ,๐Ÿ‹,๐ŸŒ,๐Ÿ‰,๐Ÿ“,๐Ÿ’,๐Ÿฅฅ,๐Ÿฅ,๐Ÿฅ‘,๐ŸŒถ ,๐Ÿง€,๐Ÿฟ,๐Ÿบ,โšฝ๏ธ,๐Ÿ€,๐Ÿ,๐ŸฅŠ,๐ŸŽน,๐Ÿฅ,๐Ÿ– ,๐Ÿ” ,โ›บ๏ธ,๐Ÿ’ป,๐Ÿ’ฟ,โ˜Ž๏ธ ,๐Ÿ“Ÿ,โณ,๐Ÿ”‹,๐Ÿงฒ,๐Ÿ”ฎ,๐Ÿชฃ ,๐Ÿ“ฆ,โค๏ธ ,๐Ÿงก,๐Ÿ’›,๐Ÿ’š,๐Ÿ’™,๐Ÿ’œ,๐Ÿ–ค,๐Ÿค,๐ŸคŽ,๐Ÿ‡ฎ๐Ÿ‡ณ ".split(',')
const activity_list = '๐ŸŽจ,๐Ÿฆฎ,๐Ÿ“š,โœ๏ธ ,๐ŸŽธ,๐Ÿ›น,๐Ÿƒ๐Ÿปโ€โ™‚๏ธ'.split(',')
const getRandom = arr => arr[arr.length * Math.random() | 0]
const timings = [
  // from, duration, emoji, highlight?, unstoppable?
  [0, 500, '๐Ÿ›Œ', true, true],
  [530, 200, getRandom(activity_list)],
  [800, 200, '๐Ÿฅช', true],
  [1300, 130, '๐Ÿ›', true],
  [1600, 100, getRandom(activity_list)],
  [1700, 130, getRandom(activity_list), true, true],
  [1900, 200, '๐Ÿ•', true],
  [2130, 200, getRandom(['๐Ÿฅฑ', '๐Ÿ˜ด']), false, true],
  [2300, 100, '๐Ÿ›Œ', false, true],
Enter fullscreen mode Exit fullscreen mode


Back to Bash again. I read a bit about Cursor Movement and started playing with simple animations. Having an infinite loop that calls the JS file gave an animated effect because of the randomness.

# storing the pid
_ps_emoji_animation & ; echo "$!" > /tmp/psanimatepid-$$
# $$ to keep track of which shell is being animated
Enter fullscreen mode Exit fullscreen mode

I wrapped it in a function that was run as a daemon and the PID was stored. The PID is necessary to stop the animation. The function also took a sleep timer as an argument so we can animate at whichever pace we like. The following gif is $ psanimate .5


This was great, but the animation needs for a single emoji, like ๐Ÿ›Œ which has to be highlighted was very different. The animation should be focussing on highlighting and I used the arrow movement for that. For this altering movement, I used the current second as the position, but with differing animation times, I had to send a boolean variable to the JS.

The animation could be stopped at any point using another function that kills the appropriate PID $ psanimate_stop. I wanted the animation to start and stop at particular times, again, a schedule. Cron job wonโ€™t work because it runs in a different shell. I could have an infinite loop running that checks the time and animates it. It worked.

while [ : ]
  time=$(date +%H%M)
  if (( $time > 2300 )); then
    psanimate .2
  elif (( $time < 500 )); then
    psanimate .1
  sleep 1800 
# wrap it in a fn and run it as a daemon
Enter fullscreen mode Exit fullscreen mode

But I very felt uneasy that the schedule data is at two places now, one in bash and another in JS. And automating animation could be really annoying, so I kept a 30-minute sleep and only for the night sleep reminder.

Overriding task emojis

I didnโ€™t want to be seeing food emojis after Iโ€™ve had my meal, I wanted a means to bypass my schedule. One can also use it to snooze. So I introduced an env variable PS_TASK_OVER that can be set to the current time. The JS code skips the scheduled emoji if this variable was set within the last 1 hour. JS schedule also had a unstoppable flag that ignores the end of a task. To make it even better, I wanted to unset this variable after 1 hour, the JS only has to care about its existence. But turned out to be a tough task when Iโ€™m hellbent on keeping the task_over as an env variable and not in a file. Finally, I found a hack using traps, it wasnโ€™t ideal but the concept was new and so I held on to it.

trap 'unset PS_TASK_OVER' SIGUSR1
( sleep 3600; kill -SIGUSR1 $p ) &
Enter fullscreen mode Exit fullscreen mode

As I learned more about traps , I wrote a nice cleanup function. This makes sure the background processes are killed and the /tmp directory is kept clean (at least cleaner).

function pscleanup {
  echo "Cleaning the animation stuff"
  unset PS_TASK_OVER
trap pscleanup EXIT
Enter fullscreen mode Exit fullscreen mode

Calling it done

I pushed my changes out but I kept improving the code. I started looking into automated notifications and voices. Notifications were an easy thing.

osascript -e 'display notification "Time to play outside" with title "๐Ÿฆฎ๐Ÿƒ๐Ÿปโ€โ™‚๏ธSunshine"'
Enter fullscreen mode Exit fullscreen mode

Mac also has a nice say command. I found documentation for controlling the voices and I started playing around with it. Milenaโ€™s Russian accent soon became my favorite. Try this in your consoleโ€ฆ

say -v Milena "You are [[rate 80]]still[[rate 100]] here?[[slnc 400;rate 165]]Go sleep you faukin nerd.[[slnc 200;rate 140]] NOW.[[slnc 1500;volm +50;rate 265]] I meant[[slnc 200;rate 100]]now"
Enter fullscreen mode Exit fullscreen mode

I started somewhere else and here I was falling in love with a Russian automated voice. I realized there was no end to this journey and even though it was hard, I had to stop somewhere. Moreover, it wasnโ€™t about command prompt anymore, this schedule-based voices and notifications are more of a cron thing anyway. And so I called it done. You can find my work on GitHub .

I hope you have a great time playing with your machine too. As for me, Iโ€™m on to my next project where I intend to hear more of Milena ๐Ÿฅฐ

Adios amigo.

Top comments (0)