shell script

Backups: one quick file backup alias

When you have a file you need to edit and you have the foresight to think, “whoa, make a copy before I destroy…” you often copy hulk.txt to hulk.txt.old (that’s using the minimum of keystrokes:

 cp hul[tab][tab] hul[tab][tab].old[enter].
Linux Backups logo

Linux Backups

Well, a week later, what do you rename your next .old file? .old2? No time to put this folder into revision control? Thought so. You can inspect that last modified time on your file with stat. Experiment with this first:

echo `stat hulk.txt | awk '/Modify:/ {print $2}'`

(*snrk* did I just get you use use Awk? OMG!)

So how does that help…more precisely, you’re asking how do I add that to a backup file name? One of many ways, and I will show you the method with least typing: use an in-place shell exansion.

cp hulk.txt .hulk.txt.`stat hulk.txt | awk '/Modify:/ {print $2}'`

STOP. What wee character did I just sneek into that filename? Hold on, first write it up in an alias so you can reuse it:

alias bu="cp hulk.txt .hulk.txt.\`stat hulk.txt | awk '/Modify:/ {print $2}'\`"

Right, the backslashes (or ‘hacks’ as I nic them) keep your statement from actually evaluating the command as soon as it’s defined. The backtick is the same as saying “bash -e …stuff...”. Anyhow, now type bu and you can backup hulk.txt again. Now type ‘ls’ and see where your backup is.

No file? And no error? Oh, right the period before name hides it (sneeky). This means the next time we accidentally do a “rm *” (which often appears when you say “rm * .old” — Computer, stop, replay with magnification: rm__*__.old ). You need a good-old-fasioned:

ls -a

It’s hiding. Let’s finish up here with your alias, properly written:

alias bu="\`cp $1 .$1.\`stat $1 | awk '/Modify:/ {print $2}'\`"

Can we do it without that crazy awk? Sure:

alias bu="\`cp $1 .$1.\`stat $1 --printf %Y '\`"

Now go make a backup…right now!

Backups: using tar and find

If you are familiar with zip files, they are the DOS version of tar files (tar = Tape Archive). The tar utility is totally intended for storing backups. A quick way to backup your home directory is:

cd /home ; tar -cvf home-jed.tar ./jed

You might see that command grab a whole lot of stuff you don’t want to keep, including all your Firefox cache files and your Trash files. Also that archive is uncompressed. Lets get it compressed as much as we can, first, that’s easy:

tar -cvjf home-jed.tbz2 ./jed

Next, we can build a list of files we want to backup using find. Please don’t try and avoid the find command, once you begin to understand it, life in Linux really can improve. On our first try, we will pair it down with fgrep (simple grep) to exclude our Firefox .Cache directory.

cd home
find jed/.mozilla/firefox \
| fgrep -v '.default/Cache' \
> /tmp/jed.txt

And following that, avoiding our trash can:

find jed/.mozilla/firefox \
| fgrep -v '.default/Cache' \
| fgrep -v '.local/share/Trash' \
> /tmp/jed.txt

Now think about why we want to use pipe operators in that second find command. Would it be easier as two commands both appending to /tmp/jed.txt? (Think about the overlap and duplication that results.)

If we wanted to use that file to guide tar, we change our tar command like so:

tar cvjf ./jed.tbz2 -T /tmp/jed.txt

In order to make regular backups a regularity, we need to make them pertinent and economical (of time and of space). We often do not want to back up ephemeral files that are byproducts of our work. If you program, you will have ready examples on your own drive: .a, .o, .out, .class code files often do not need to be kept if you make them several times a day.

Consider the example below. With it we can backup the substantive slice of our code tree to another drive on our system. We avoid the ephemeral files. We also chose to backup our code separately from the rest of our home directory. By doing this we can schedule code tree backups every hour, and schedule our home tree backups just once a day.

#!/bin/bash
function CodeSnap() {
    local now=`date +%Y-%m-%d.%H%M`
    local arcnom="/mnt/backup/code.$now.tbz2"
    local flist="/tmp/code.$now.txt"
    find ~/code -type f -a\
      \( -name '*.xml' \
      -o -name '*.java' \
      -o -name '*.properties' \
      -o -name '*.php' \
      -o -name '*.pl' \
      -o -name '*.conf' \
      -o -name '*.pm' \
      -o -name '*.c' \
      -o -name '*.h' \
      -o -name '*sh' \
      -o -name '[Mm]ake*' \
      \) > $flist
    find ~/Documents -type f -a\
      \( -name '*.php' \
      -o -name '*.pl' \
      -o -name '*.conf' \
      -o -name '*.pm' \
      \) >> $flist

    tar vcjf $arcnom -T $flist
}
##
## Copyright (C) 2013, Jed Reynolds
## Free for non commercial use.
##
CodeSnap

Questions? I hope! You just saw a full strength, professional level bash script. If you don’t have questions, show me your script.

Return of the 007 SSH Agent

Years ago when I first stared listening to podcasts when I had barely worked at PRWeb for a year even, I came up with a piece of shell script to automatically start up an ssh-agent and ask for your passphrase.

Unfortunately, the code created tons of ssh-agents, which was unfortunate.

Here is a version that behaves much better:

  1 #!/bin/bash
2 export SSH_RECENT="$HOME/.ssh/recent"
3 [ -f $SSH_RECENT ] && eval `cat $SSH_RECENT`
4 RUNNING_AGENTS=0
5 if [ ! -z "$SSH_AGENT_PID" ]
6 then
7 RUNNING_AGENTS=`ps -p $SSH_AGENT_PID | grep -v CMD | wc -l`
8 fi
9 if [ $RUNNING_AGENTS -lt 1 -a $UID -ne 0 ]
10 then
11 eval `ssh-agent`
12 echo "export SSH_AGENT_PID=$SSH_AGENT_PID" > $SSH_RECENT
13 echo "export SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> $SSH_RECENT
14 fi
15
16 [ `ssh-add -l | fgrep -v ' no ' | wc -l` -lt 1 ] && ssh-add

Can you tell me why I’m choosing to evaluate $UID for zero?

And, will this work if I switch from an Xterm to a virtual terminal?