I commonly backup config files on servers without version control like so:

# cp httpd.conf{,.bak}
# ls
httpd.conf    httpd.conf.bak

However, for files in the web root I take the time to carefully put the .bak before the filename extension so that the .php will remain and Apache will still send the file through the PHP interpreter if someone manages to guess or probe the server for such files:

$ cp index.php index.bak.php
$ ls
index.php    index.bak.php

Is there any way to use tab-completion and muscle memory to put the .bak before the filename extension? I have Tab{}←,.bak burned into muscle memory and I cannot use this nice "meat macro" on files in the web root.


  • 3
    The real answer is to use version control. Feb 28 '13 at 9:30
  • 100% Gilles! For one reason or another not every server is using version control, though I am trying to push in that direction.
    – dotancohen
    Feb 28 '13 at 9:38
  • 1
    If for some reason you cannot get version control now, get your backups out of the document root, and/or configure your httpd to deny requests to files with the .bak extension.
    – Konerak
    Feb 28 '13 at 12:34
  • Yes, the 'backup' file is just a CMA for making a small change, which I should rm as soon as I'm done. But you are right, I should configure .htaccess to deny .bak files. Thanks.
    – dotancohen
    Feb 28 '13 at 13:15

Might be as simple as

cp file.{ext,bak.ext}

bash {} expansion expands to all combinations (unlike [] expantion which lists existing files. So it generates two file names.

The { .... , .... } provides two options, so you get file.ext and file.bak.ext

In your script you would use


And to break up the FILENAME into the two halves you can use


${VAR%xxxxx} will return the left side of what is in VAR up to the matching pattern, in the case above the pattern is .* so you get everything except the last .*

${VAR##xxxxx} will return the right side of what is in VAR, after the matching pattern. The double hash means get the biggest possible match, in case there is more than one possble match.

To make your script save, check for cases where after obtaining FILE and EXTENTION, that they are not the same thing. This will happen in cases where the file name doesn't have an extention.

A sub-section of your script might look like this:

ls *html | while read FILENAME
    cp $FILE{$EXTN,.bak$EXTN}
  • Note that the section where the sub-script tests to see if $FILE is the same as $EXTN simply squashes EXTN. I notice there is a mistake, it requires that the . be inside the EXTENTION variable... A slight modification will take care of that automatically...
    – Johan
    Feb 28 '13 at 7:50
  • In this final version the script correctly handles files without an extention too. It will generate two names, eg file and file.bak in those cases.
    – Johan
    Feb 28 '13 at 7:56
  • 7
    Why not just cp file{,.bak}.ext then?
    – Bernhard
    Feb 28 '13 at 8:19
  • @Bernhard Heh why didn't I think of that!?
    – Johan
    Feb 28 '13 at 8:21
  • @Bernard: Perfect! Now I need to find a standard (i.e. non-VIM) Bash shortcut to go back to the previous . character and I'll be in backup Nirvana.
    – dotancohen
    Feb 28 '13 at 9:45

You can always use a function:

  for i do
    case ${i##*/} in
      (?*.?*) cp -iv -- "$i" "${i%.*}.bak.${i##*.}";;
      (*) cp -iv -- "$i" "$i.bak"

${i##*/} is the basename of $i (that is $i with everything up to the rightmost / removed), because we don't want to consider bar/file as the extension in foo.bar/file.

Then we check whether $i contains an extension, that is if it contains at least one dot character with characters on both sides (?* is any one character followed by 0 or more characters, so it's one or more characters). We can't do *.* otherwise we'd backup ~/.bashrc as ~/.bak.bashrc for instance.

That syntax above is POSIX except for the -v (for verbose) option to cp which is a GNU extension also found in a few other implementations like BSDs'.

Then you can do things like:

backup foo.php bar.html baz<Tab>...


backup *.php

Without having to worry about losing data (-i will ask for confirmation before overwriting a file, see also the -b option with GNU cp) or not picking the right name.

  • 1
    Could you please break down the (?*.?*) and (*)? Thank you.
    – jasonwryan
    Feb 28 '13 at 7:56

I can't offhand think of a way to do this with tab completion, except possibly to also keep a copy of the file name without the extension, so you can add "bak.php" in one go. But my solution to this would be a small shell script, that takes a file name as input and copies it to "firstpart.bak.extension".

  • +1 for the shell script, which I'm trying to write into a BASH function as I'd like to take my .bashrc with me anyway.
    – dotancohen
    Feb 28 '13 at 9:41
  • Actually it'd probably be easier to call it "bak.firstpart.extension". Then you don't need to bother with parsing parts of name.
    – Jenny D
    Feb 28 '13 at 9:54
  • Thanks, but then the files won't sort together.
    – dotancohen
    Feb 28 '13 at 10:30
  • Good point, I didn't think of that.
    – Jenny D
    Feb 28 '13 at 10:33

I have this issue for several unix systems with no source control with a requirement to make small changes fast. For cases like this I hit the file with a script to cut a time stamped copy of it to allow for quick recovery. ls shows the files with the versions date sorted by file name after it.

if [ "$1" = "" ]; then
   echo "File name required. Aborting."
   exit 1

CDATE=`date +%Y-%m-%d-%H-%M`
cp "$1" "$1-$CDATE"
echo File backed up to: $1-$CDATE


VERS.sh httpd.conf


File backed up to: httpd.conf-2016-07-14-10-27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.