Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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.

Thanks.

share|improve this question
3  
The real answer is to use version control. –  Gilles 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
add comment

3 Answers

up vote 3 down vote accepted

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

cp $FILE.{$EXTENTION,bak.$EXTENTION}

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

FILE=${FILENAME%.*}
EXTN=${FILENAME##*.}

${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
do
    FILE=${FILENAME%.*}
    EXTN=${FILENAME##$FILE}
    cp $FILE{$EXTN,.bak$EXTN}
done
share|improve this answer
    
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
5  
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
show 2 more comments

You can always use a function:

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

${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>...

Or:

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.

share|improve this answer
1  
Could you please break down the (?*.?*) and (*)? Thank you. –  jasonwryan Feb 28 '13 at 7:56
    
Thanks for the update: that is very cool... –  jasonwryan Feb 28 '13 at 8:19
add comment

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".

share|improve this answer
    
+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
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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