Error 'rm: missing operand' when using along with 'find' command - bash

I see that this question is getting popular.
I answered my own question below.
What says Inian is correct and it helped me to analyze my source code better.
My problem was in the FIND and not in the RM. My answer gives a block of code, which I am currently using, to avoid problems when FIND finds nothing but still would pass arguments to RM, causing the error mentioned above.
OLD QUESTION BELOW
I'm writing many and many different version of the same command.
All, are executed but with an error/info:
rm: missing operand
Try 'rm --help' for more information.
These are the commands I'm using:
#!/bin/bash
BDIR=/home/user/backup
find ${BDIR} -type d -mtime +180 -print -exec rm -rf {} \;
find ${BDIR} -type d -mtime +180 -print -exec rm -rf {} +
find "$BDIR" -type d -mtime +180 -print -exec rm -rf {} \;
find "$BDIR" -depth -type d -mtime +180 -print -exec rm -rf {} \;
find ${BDIR} -depth -type d -mtime +180 -print -exec rm -rf {} +
find $BDIR -type d -mtime +180 -print0 | xargs -0 rm -rf
DEL=$(FIND $BDIR -type d -mtime +180 -print)
rm -rf $DEL
I'm sure all of them are correct (because they all do their job), and if I run them manually I do not get that message back, but while in a .sh script I do.
EDIT: since I have many of these RM's, the problem could be somewhere else. I'm checking all of them. All of the above codes works but the best answer is the one marked ;)

The problem is when using find/grep along with xargs you need to be sure to run the piped command only if the previous command is successful. Like in the above case, if the find command does not produce any search results, the rm command is invoked with an empty argument list.
The man page of xargs
-r Compatibility with GNU xargs. The GNU version of xargs runs the
utility argument at least once, even if xargs input is empty, and
it supports a -r option to inhibit this behavior. The FreeBSD
version of xargs does not run the utility argument on empty
input, but it supports the -r option for command-line compatibil-
ity with GNU xargs, but the -r option does nothing in the FreeBSD
version of xargs.
Moreover, you don't to try all the commands like you pasted the below simple one will suit your need.
Add the -r argument to xargs like
find "$BDIR" -type d -mtime +180 -print0 | xargs -0 -r rm -rf

-f option of rm suppresses the rm: missing operand error:
-f, --force
ignore nonexistent files and arguments, never prompt

After researches, the command I'm comfortable using is:
HOME=/home/user
FDEL=$HOME/foldersToDelete
BDIR=/backup/my_old_folders
FLOG=/var/log/delete_old_backup.log
find ${BDIR} -mindepth 1 -daystart -type d -mtime +180 -printf "%f\n" > ${FDEL}
if [[ $? -eq 0 && $(wc -l < ${FDEL}) -gt 0 ]]; then
cd ${BDIR}
xargs -d '\n' -a ${FDEL} rm -rf
LOG=" - Folders older than 180 were deleted"
else
LOG=" - There aren't folders older than 180 days to delete"
fi
echo ${LOG} >> ${FLOG}
Why? I search all the old folders I want to delete and print them all into a file, regardless for their naming with or without space. If the file is bigger than 0 byte this means that there are folder I want no more.
If your 'FIND' fails with a 'rm: missing operand', it probably isn't to search in the RM rather in the FIND itself.
A good way of removing the file using FIND, is the one I felt to share with you.

Related

Removing files with/without spaces in filename

Hello stackoverflow community,
I'm facing a problem with removing files that contain spaces in filename, i have this part of code which is responsible of deleting files that we get from a directory,
for f in $(find $REP -type f -name "$Filtre" -mtime +${DelAvtPurge})
do
rm -f $f
I know that simple or double quotes are working for deleting files with spaces, it works for me when i try them in a command line, but when i put them in $f in the file it doesn't work at all.
Could anybody help me to find a solution for this ?
GNU find has -delete for that:
find "$REP" -type f -name "$Filtre" -mtime +"$DelAvtPurge" -delete
With any other find implementation, you can use bulk-exec:
find "$REP" -type f -name "$Filtre" -mtime +"$DelAvtPurge" -exec rm -f {} +
For a dry-run, drop -delete from the first and see the list of files to be deleted; for second, insert echo before rm.
The other answer has shown how to do this properly. But fundamentally the issue in your command is the lack of quoting, due to the way the shell expands variables:
rm -f $f
needs to become
rm -f "$f"
In fact, always quoting your variables is safe and generally a good idea.
However, this will not fix your code. Now filenames with spaces will work, but filenames with other valid characters (to wit, newlines) won’t. Try it:
touch foo$'\n'bar
for f in $(find . -maxdepth 1 -name foo\*); do echo "rm -f $f"; done
Output:
rm -f ./foo
rm -f bar
Clearly that won’t do. In fact, you mustn’t parse the output of find, for this reason. The only way of making this safe, apart from the solution via find -exec is to use the -print0 option:
find "$REP" -type f -name "$Filtre" -mtime +"$DelAvtPurge" -print0 \
| IFS= while read -r -d '' f; do
rm -f "$f"
done
Using -print0 instead of (implicit) -print causes find to delimit hits by the null character instead of newline. Correspondingly, IFS= read -r -d '' reads a null-character delimited input string, which we do in a loop using while (the -r option prevents read from interpreting backslashes as escape sequences).

copy files with the base directory

I am searching specific directory and subdirectories for new files, I will like to copy the files. I am using this:
find /home/foo/hint/ -type f -mtime -2 -exec cp '{}' ~/new/ \;
It is copying the files successfully, but some files have same name in different subdirectories of /home/foo/hint/.
I will like to copy the files with its base directory to the ~/new/ directory.
test#serv> find /home/foo/hint/ -type f -mtime -2 -exec ls '{}' \;
/home/foo/hint/do/pass/file.txt
/home/foo/hint/fit/file.txt
test#serv>
~/new/ should look like this after copy:
test#serv> ls -R ~/new/
/home/test/new/pass/:
file.txt
/home/test/new/fit/:
file.txt
test#serv>
platform: Solaris 10.
Since you can't use rsync or fancy GNU options, you need to roll your own using the shell.
The find command lets you run a full shell in your -exec, so you should be good to go with a one-liner to handle the names.
If I understand correctly, you only want the parent directory, not the full tree, copied to the target. The following might do:
#!/usr/bin/env bash
findopts=(
-type f
-mtime -2
-exec bash -c 'd="${0%/*}"; d="${d##*/}"; mkdir -p "$1/$d"; cp -v "$0" "$1/$d/"' {} ./new \;
)
find /home/foo/hint/ "${findopts[#]}"
Results:
$ find ./hint -type f -print
./hint/foo/slurm/file.txt
./hint/foo/file.txt
./hint/bar/file.txt
$ ./doit
./hint/foo/slurm/file.txt -> ./new/slurm/file.txt
./hint/foo/file.txt -> ./new/foo/file.txt
./hint/bar/file.txt -> ./new/bar/file.txt
I've put the options to find into a bash array for easier reading and management. The script for the -exec option is still a little unwieldy, so here's a breakdown of what it does for each file. Bearing in mind that in this format, options are numbered from zero, the {} becomes $0 and the target directory becomes $1...
d="${0%/*}" # Store the source directory in a variable, then
d="${d##*/}" # strip everything up to the last slash, leaving the parent.
mkdir -p "$1/$d" # create the target directory if it doesn't already exist,
cp "$0" "$1/$d/" # then copy the file to it.
I used cp -v for verbose output as shown in "Results" above, but IIRC it's also not supported by Solaris, and can be safely ignored.
The --parents flag should do the trick:
find /home/foo/hint/ -type f -mtime -2 -exec cp --parents '{}' ~/new/ \;
Try testing with rsync -R, for example:
find /your/path -type f -mtime -2 -exec rsync -R '{}' ~/new/ \;
From the rsync man:
-R, --relative
Use relative paths. This means that the full path names specified on the
command line are sent to the server rather than just the last parts of the
filenames.
The problem with the answers by #Mureinik and #nbari might be that the absolute path of new files will spawn in the target directory. In this case you might want to switch to the base directory before the command and go back to your current directory afterwards:
path_current=$PWD; cd /home/foo/hint/; find . -type f -mtime -2 -exec cp --parents '{}' ~/new/ \; ; cd $path_current
or
path_current=$PWD; cd /home/foo/hint/; find . -type f -mtime -2 -exec rsync -R '{}' ~/new/ \; ; cd $path_current
Both ways work for me at a Linux platform. Let’s hope that Solaris 10 knows about rsync’s -R ! ;)
I found a way around it:
cd ~/new/
find /home/foo/hint/ -type f -mtime -2 -exec nawk -v f={} '{n=split(FILENAME, a, "/");j= a[n-1];system("mkdir -p "j"");system("cp "f" "j""); exit}' {} \;

Shell stop script if find command fails

Good day.
In a script of fine i have the following find command:
find -maxdepth 1 \! -type d -name "some_file_name_*" -name "*.txt" -name "*_${day_month}_*" -exec cp {} /FILES/directory1/directory2/directory3/ +
I want to know how to stop the script if the command does't find anything.
Use GNU xargs with the -r switch and a pipeline to ensure the output of find is passed to cp only if its non-empty.
find -maxdepth 1 \! -type d -name "some_file_name_*" -name "*.txt" -name "*_${day_month}_*" \
| xargs -r I{} cp "{}" /FILES/directory1/directory2/directory3/
I{} is a place-holder for the output from the find command which is passed to cp,
The flags, -r and I{} represent the following according to the man xargs page,
-r, --no-run-if-empty
If the standard input does not contain any nonblanks, do not run
the command. Normally, the command is run once even if there is
no input. This option is a GNU extension.
-I replace-str
Replace occurrences of replace-str in the initial-arguments with
names read from standard input.
You may add -exec false {} so you get a false exit status when something is found (which makes it a bit upside-down though)
if find . -name foo -exec echo ok ';' -exec false {} +
then
echo 'not found'
exit
fi
echo found
See similar question in stackexchange: How to detect whether “find” found any matches?, in particular this answer which suggests the false trick

How to print the deleted file names along with path in shell script

I am deleting the files in all the directories and subdirectories using the command below:
find . -type f -name "*.txt" -exec rm -f {} \;
But I want to know which are the files deleted along with their paths. How can I do this?
Simply add a -print argument to your find.
$ find . -type f -name "*.txt" -print -exec rm -f {} \;
As noted by #JonathanRoss below, you can achieve an equivalent result with the -v option to rm.
It's not the scope of your question, but more generally it gets more interesting if you want to delete directories recursively. Then:
a simple -exec rm -r argument keeps it silent
a -print -exec rm -r argument reports the toplevel directories you're operating on
a -exec rm -rv argument reports all you're removing

execdir and rename commands together?

I ask a question about finding, copying and renaming which can be found here Find, copy, rename within the same directory
The answer was great and solved the issue I had in that thread but it did bring up another question about how I can rename just part of the file....for example when running this command;
find /home/ian/Desktop/TEST/ -type f -mmin -1 -execdir echo cp \{} \{}_backup \;
and the file is called TEST_MASTER how can you run the above and have the new file called TEST_BACKUP as opposed to TEST_MASTER_BACKUP?
I can solve this by running a new rename command straight after like below;
find /home/ian/Desktop/TEST/ -type f -mmin -1 -execdir cp \{} \{}_backup \; ;
rename __MASTER_backup _backup *MASTER_backup ;
but there must be a way to do this in one go?
All the best,
Ian
You can use this find command:
find /home/ian/Desktop/TEST/ -type f -mmin -1 -execdir bash -c 'cp "$1" "${1%%_*}_BACKUP"' - '{}' \;
I came with almost the same answer as anubhava:
find /home/ian/Desktop/TEST/ -type f -name '*_MASTER' -mmin -1 -execdir \
bash -c 'mv $1 ${1/MASTER/BACKUP}' - \{} \;
This will only backup *_MASTER files. If you need to backup the other files as well (and add an extra _BACKUP at the end, vote for anubhava!

Resources