My First Sed Script (That Became a Bash Script?)

Last post, I wrote about a Sed script I wrote to help me switch configurations. As always, I’ve learned a little in the meantime, and I’ve updated the script to reflect that. As a result, it’s not a Sed script anymore, but a Bash script that calls on Sed directly to perform tasks.

I noticed that I was not being very efficient. For one, I created three files to do something that could be done in one. For another, two of those files (the Sed scripts) were virtually identical, with only two words being changed in four instances. When saw what I had done, I was reminded of a thing I read somewhere, but sadly cannot recall where:

If you’re doing something more than once, you’re not using a computer correctly.

Anonymous?

It’s a little provocative, of course, but I like the central idea of it. A computer is meant to automate tasks, and if you, the human user, are repeatedly performing the same task, surely you should let the computer handle that? Furthermore, there’s an added risk of error: I could edit one file and forget to edit the others to match. Or, of course, were I to write a much, much larger program, copy-pasting the same code only leads to a less legible program in the end.

So, I figured to replace the repeated words with a variable, set in the Bash script, and that reduced the need for two separate Sed scripts. Now that I was down to just the one small script, I realized I could just put it directly in Bash. I would lose the Sed syntax highlighting in Vim (as it would be highlighting Bash), but to be fair the Sed highlights aren’t that great anyway. So, long story short, I ended up with the following, single script:

#!/bin/bash
# switch_config.sj -- Bash script to switch over i3wm configs
# $1 = switch argument

# Check what file to change
case $1 in
    -d|--desktop) to=Desktop
	    	  from=Laptop
		  ;;
    -l|--laptop)  to=Laptop
	    	  from=Desktop
		  ;;
    *) echo "Select either -d, --desktop, -l, or --laptop"; exit 1
	    	  ;;
esac

# Back up file, using \cp to avoid interactive alias
\cp config -f config.old

# Catch potential errors
if [ $# != 1 ]
	then echo "Error: please provide one argument."; exit 1
fi


echo "Swapping i3wm configuration over from $from to $to."

# Using sed, comment out one set, and uncomment the other
sed -i '
/# '"$from"'/,/^$/{
	/# '"$from"'/b
	/^$/b
	/^#/!s/\(.*$\)/\#\1/
}

/# '"$to"'/,/^$/{
	/# '"$to"'/!s/#//
}
' config

My First Sed script

2020.07.22 edit: There’s a followup to this post, that you can read here. It has an updated version of the script.

For the past weeks, I’ve been reading through O’Reilly’s Sed & Awk, 2nd Edition, to learn those powerful commandline tools. The other day, I had a practical problem for which Sed seemed like the perfect solution out of the box. It’s small, and quite trivial, but it made me proud nonetheless.

In brief, the situation I run into is that I run Manjaro Linux using the i3WM windows manager on both my desktop and laptop, and I would like to share configuration files between them; however, due to their different resolutions, I want most but not all settings to be shared. Specifically, I have a few windows I keep on a scratchpad (basically: floating windows that you can hide, as well as keep consistent across workspaces). I still tweak these files every so often as I try to add features I like or adjust the look of this or that. I then share the config file between my two PCs, which leads me to having to adjust a few settings:

 # Laptop configuration
 for_window [class="^popup_term$"] resize set 960 540
 for_window [class="^popup_note$"] resize set 455 758
 bindsym $mod+equal [class="^popup_note$"] scratchpad show, move position 906 5
  
 # Desktop configuration
 #for_window [class="^popup_term$"] resize set 960 540
 #for_window [class="^popup_note$"] resize set 455 1070
 #bindsym $mod+equal [class="^popup_note$"] scratchpad show, move position 2540 324

By the way, you can see the constant tweaks here, because I used to have the popup-term class take different sizes depending on PC or laptop, but I recently settled on using the same resolutions for both. So, that one, for instance, will have to be edited out again into the general settings. The settings above used to be part of those settings, but I split them off to separate the desktop/laptop-specific configurations. Then, using my newly-gained Sed skills I made a tiny script that just comments out one set of code and removes the comments for the other:

# Commands to switch to desktop configuration

# Comment out laptop configurations
/# Laptop/,/^$/{
        /# Laptop/b
        /^$/b
        /^#/!s/\(.*$\)/\#\1/
}

# Uncomment desktop configurations
/# Desktop/,/^$/{
        /# Desktop/!s/#//
}

It’s a fairly straightforward substitution: anything between the start of the Laptop section to the first blank line gets subjected to the code. Using a branch to nowhere (using a branch without a label in Sed means it skips to the end of the script), I make sure to skip the title (I want to keep that a comment), and to skip the blank line (just to keep it looking neat). Then, a basic regex takes every line that doesn’t start with a comment and replaces those lines with a # and the line. Lastly, we do the inverse in the Desktop section: we simply replace the # character with nothing in all lines except the title. Since we’re only removing a character, there’s no need to filter out anything else.

Because I have two scripts, one for the move to desktop and one to laptop, I made a small bash script to switch between them:

#!/bin/bash
# Bash script to switch over i3wm configs

computer=

# Back up file, using \cp to avoid interactive alias
\cp config -f config.old

# Catch potential errors
if [ $# != 1 ]
        then echo "Error: please provide one argument."; exit 1
fi

# Check what file to change
case $1 in
    -d|--desktop) computer=desktop;;
    -l|--laptop) computer=laptop;;
    *) echo "Select either -d, --desktop, -l, or --laptop"; exit 1
esac

echo "Swapping configuration over to $computer."

# Implement the change using sed
if [ $computer == desktop ]
        then sed -i -f desktop.sed config
        else sed -i -f laptop.sed config
fi

This way, I don’t have to take the individual steps myself, and just use the one command to take care of it. Plus, if one day I figure out a way to check if the config file has changed from a previous version, I could use this to automate an adjustment. For now, I’m just happy I figured out a practical means of automating something I used to have to do manually each time I edited the configuration file.