rm works on command line but not in script

There are various possible failure points in your script. First of all, rm *.old* will use globbing to create a list of all matching files, and that can deal with file names containing whitespace. Your script, however, assigns a variable to each result of the glob and does that without quoting. That will break if your file names contain whitespace. For example:

$ ls
'file name with spaces.old.txt'  file.old.txt
$ rm *.old.*   ## works: both files are deleted

$ touch "file.old.txt" "file name with spaces.old.txt"
$ for i in ./*; do oldfile=$i; rm -v $oldfile; done
rm: cannot remove './file': No such file or directory
rm: cannot remove 'name': No such file or directory
rm: cannot remove 'with': No such file or directory
rm: cannot remove 'spaces.old.txt': No such file or directory
removed './file.old.txt'

As you can see, the loop failed for the file with spaces in its name. To do it correctly, you would need to quote the variable:

$ for i in ./*; do oldfile="$i"; rm -v "$oldfile"; done
removed './file name with spaces.old.txt'
removed './file.old.txt'

The same issue applies to pretty much every use of $i in your script. You should always quote your variables.

The next possible issue is that you seem to expect that *.old.* matches files with the extension .old. It doesn't. It matches "0 or more characters" (*), then a ., then "old", then another . and then "0 or more characters again". This means that it will not match something like file.old, but only something like `file.old.foo:

$ ls
file.old  file.old.foo
$ for i in *; do if [[ "$i" == *.old.* ]]; then echo $i; fi; done
file.old.foo     

So, no match foe file.old. In any case, your script is far more complex than needed. Try this one instead:

#!/bin/bash

for i in *; do
    if [[ -f "$i" ]];  then
        if [[ "$i"  ==  *.old ]]; then
            rm -v "$i" || echo "rm failed for $i"
        else
            echo "$i doesn't have an .old extension"
        fi
        cp -v "$i" "$i".old
    else
        echo "$i is not a file"
    fi 
done

Note that I added -v to the rm and cpwhich does the same thing as what you were doing with yourecho` statements.

This isn't perfect since when you find, for example, file.old, that will be removed and, later on, the script will try to copy it and fail since the file no longer exists. However, you haven't explained what you script is actually attempting to do so I can't fix that for you unless you tell us what you are really trying to accomplish.

If what you want is to i) remove all files with the .old extension and ii) add the .old extension to any existing files that don't have it, all you really need is:

#!/bin/bash

for i in *.old; do
    if [[ -f "$i" ]]; then
        rm -v "$i" || echo "rm failed for $i"
    else
        echo "$i is not a file"
    fi 
done
## All the ,old files have been removed at this point
## copy the rest
for i in *; do
    if [[ -f "$i" ]]; then
        ## the -v makes cp report copied files
        cp -v "$i" "$i".old
    fi
done

The only cases rm $oldfile could fail are when your filename contains any character of IFS (space, tab, newline) or any glob character (*, ?, []).

If any character of IFS is present the shell will perform word splitting and based on the presence of globbing characters pathname expansion on the variable expansion.

So, for example, if the file name is foo bar.old., variable oldfile would contain foo bar.old..

When you do:

rm $oldfile

shell at first splits the expansion of oldfile on space into two words, foo and bar.old.. So the command becomes:

rm foo bar.old.

which would obviously lead to unexpected result. By the way, if you have any globbing operators (*, ?, []) in the expansion, then pathname expansion would be done too.

You need to quote the variables to get desired result:

rm "$oldfile"

Now, no word splitting or pathname expansion would be done, hence you should get desired result i.e. the desired file would be removed. If any filename happens to start with -, then do:

rm -- "$oldfile"

You might ask, why we don't need to quote variables when used inside [[, the reason being [[ is a bash keyword and it handles variable expansion internally keeping the expansion literal.


Now, couple of points:

  • You should redirect STDERR (exec 2>errorfile) before the rm command otherwise [[ -s errorfile ]] test would give false positives

  • You have used [ -s $errorfile ], you are using a variable expansion $errorfile, which would be NUL given errorfile variable is not defined anywhere. Perhaps you meant, just [ -s errorfile ], based on the STDERR redirection

  • If the variable errorfile is defined, while using [ -s $errorfile ], it would again choke on above mentioned cases of IFS and globbing because unlike [[, [ is not handled internally by bash

  • In the later part of the script, you are trying to cp the already removed file (again without quoting the variable), this does not make any sense, you should check that chuck and make necessary corrections based on your target.


If I understand what you are doing (delete any files with the .old suffix, and make a copy of any existing files with a .old suffix), you could use find instead:

#!/bin/sh

find . -maxdepth 1 -name \*.old -type f -printf "deleting %P\n" -delete
find . -maxdepth 1 -type f -printf "copying %P to %P.old\n" -exec cp '{}' '{}.old' \;

-maxdepth 0 stops the find command looking in subdirectories, -type f looks for regular files only; -printf creates messages (%P is the filename found). The -exec cp calls the copy function and '{}' is the filename

Tags:

Bash

Rm

Scripts