How can I get file found by find command to current dir? - bash

I have tried this command:
cd ~/Desktop
sudo find /tmp -name *file.doc* -exec 'mv "{}" ./file.docx' '{}' \;
but error:
find: mv "a b cfile.docx" ./file.docx: No such file or directory'
But the mv command looks just as it should (first arg - quoted file found in /tmp dir, second arg - new name for that file in current directory - that is mv SOURCE DEST is correct). If I do it in the /tmp dir - it would work, so why the error?

Find is trying to execute the file 'mv "a b cfile.docx" ./file.docx' but there's no such file.
This is what is happening:
$ 'mv "a b cfile.docx" ./file.docx'
-bash: mv "a b cfile.docx" ./file.docx: No such file or directory
bash also is not able to find the file we've asked it to execute.
The intention is to execute mv. Try this fixed version.
I've removed the single quote surrounding the arg to -exec
also the {} in the end is not needed. we just need the file substituted in one place
sudo find /tmp -name *file.doc* -exec mv "{}" ./file.docx \;

Related

How do I rename files found with the find command

I have a series of music folders. Some of the file names contain an underscore which I would like to get rid of.
With
find /Users/Chris/CDs -type f -name "*_*"
I find all of the files with underscores.
it appears that I can add -execdir mv {} to the command but do not know what to add from there.
I think {} provides the full path and file name as a string of the file with underscores but I do not know how to use something like sed 's/_//g' to remove the _ on the new file name.
Any help would be greatly appreciated.
Try:
find /Users/Chris/CDs -type f -name "*_*" -execdir bash -c 'mv -i -- "$1" "${1//_/}"' Mover {} \;
How it works:
-execdir bash -c '...' Mover {} \;
This starts up bash and tells it to run the command in the single quotes with Mover assigned to $0 and the file name assigned to $1.
mv -i -- "$1" "${1//_/}"
This renames file $1. This uses bash's parameter expansion feature, ${1//_/}, to create the target name from $1 by removing all underlines.
The option -i tells mv to ask interactively before overwriting a file.
The option -- tells mv that there are no more options. This is needed so that files whose names begin with - will be processed correctly.
Example
Let's start with a directory with these files:
$ ls
1_2_3_4 a_b c_d
Next we run our command:
$ find . -type f -name "*_*" -execdir bash -c 'mv -i -- "$1" "${1//_}"' Mover {} \;
After the command completes, the files are:
$ ls
1234 ab cd
The purpose of $0
Observe this command where we have added an error:
$ find . -type f -name "*_*" -execdir bash -c 'foobar -i -- "$1" "${1//_}"' Mover {} \;
Mover: foobar: command not found
Note that Mover appears at the beginning of the error message. This signals that the error comes from within the bash -c command.
If we replace Mover with -, we would see:
$ find . -type f -name "*_*" -execdir bash -c 'foobar -i -- "$1" "${1//_}"' - {} \;
-: foobar: command not found
When running a single command in a terminal, the source of the error may still be obvious anyway. If this find command were buried inside a long script, however, the use of a more descriptive $0, like Mover or whatever, could be a big help.

find file in subfolder, move to other directory, and delete subfolder after moving with find -exec

I am looking for the following:
I want to search files with .m4a in all subfolders in my directory. When a file with this ending was found, I would like to move it to another folder. But afterwards I would like to delete this subfolder. I am stuck with the last step.
So far I have:
find . -name '*.m4a' -type f -exec mv {} /path/to/storage \;
I don't know how to delete the subfolder, the m4a file was found in. Also I don't want to delete by accident any other folder (I mean in case I apply the rm command wrongly. Deleting a subfolder, where I found a m4a file in, is okay). Basically, I am not sure how to control the deleting and would I be able to add this command with another -exec in the find command, please?
Thank you!
Something like this should be ok.
If you are happy with the results, you can remove the echo to perform real actions:
find . -name '*.m4a' -type f -exec sh -c 'echo "mv {} /path/to/storage";echo "rm -r "$(dirname {})"" ' \;
Be carefull that if the /path/to/storage is a subdirectory under the current working working you performed find, will also be removed , like this:
$ pwd
/home/gv/Desktop/PythonTests/tmp/tmp2
$ find . -name 'test.txt' -exec sh -c 'echo "mv {} /home/gv/Desktop/PythonTests/tmp/tmp2";echo "rm -r "$(dirname {})"" ' \;
mv ./tmp3/test.txt /home/gv/Desktop/PythonTests/tmp/tmp2 #indeed test.txt was in subfolder tmp3 under cwd tmp2
rm -r ./tmp3 #this is good - file moved , tmp3 removed also
mv ./test.txt /home/gv/Desktop/PythonTests/tmp/tmp2 #oops. find returned the result after previous mv , now file is found under tmp2
rm -r . #tmp2 gone also!

Bash: Batch rename nested directories or files with the same name

I would like to rename the following directory:
From 1/2/3/2/2 to 1/2_re/3/2_re/2_re.
Each directory has other contents too -for example file2stay.sh- which should stay untouched.
I tried the command:
find ./ -exec bash -c 'mv 2 2_re' \; but after it successfully renames the first directory the following error message appears:
mv: cannot stat ‘2’: No such file or directory
You need to tell find to process the content of a folder before the folder itself using -depth:
find . -name "2" -type d -depth -execdir mv 2 2_re \;
-execdir executes the mv in the folder where the ./2 was found.
You can use this find with sort -r in a for loop using process substitution:
while read -r f; do
mv "$f" "${f}_re"
done < <(find . -name '2' | sort -r)

Error message when using find -exec to copy files

I use the find command to copy some files from one destination to another. If I do
$ mkdir dir1 temp
$ touch dir1/dir1-file1
$ find . -iname "*file*" -exec cp {} temp/ \;
everything works fine as expected, but if I do
$ mkdir SR0a temp
$ touch SR0a/SR0a-file1
$ find . -iname "*file*" -exec cp {} temp/ \;
> cp: `./temp/SR0a-file1' and `temp/SR0a-file1' are the same file
I get an error message. I do not understand this behavior. Why do I get an error by simply changing names?
That is because find searchs in SR0a/ folder at first, and then in temp/, and since you have copied into it the file, find founds it again in temp/ folder. It seems that find uses crafty sorting so it just should be take into account on use of find:
$ mkdir temp dir1 SR0a DIR TEMP
$ find .
.
./TEMP
./SR0a
./temp
./dir1
./DIR
So in case the dir1/ find founds the it at first, and this don't make such problems, let see the search sequence:
temp/
dir1/
When you search with SR0a the sequence is:
SR0a/
temp/
so found file is being copied into temp before searching it.
To fix it, either move temp/ folder outside the current one:
$ mkdir SR0a ../temp
$ touch SR0a/SR0a-file1
$ find . -iname "*file*" -exec cp {} ../temp/ \;
or use pipe to separate find and copy procedures:
$ find . -iname "*file*" | while read -r i; do cp "$i" temp/; done
This find should work:
find . -path ./temp -prune -o -iname "*file*" -type f -exec cp '{}' temp/ \;
-path ./misc -prune -o is used to skip ./temp directory while copying files to temp folder.
Your find command is also finding ./temp/*file* files and trying to copy them also into ./temp folder.
It is caused by the find that is trying to copied to it self.
Pipe output using while to separate with find command
Use cp with the option: -frpvT for match with file/dir target path
Print the realpath of the ouput file, see if the file path are the same.
find . -iname "*file*" | while read -r f; do echo cp -frpvT "$(realpath $f)" "/temp/$f"; done
If so, then correct the file path, when it is done then you can remove the echo from the command.

Bash script, run echo command with find, set a variable and use that variable

I want to run two commands but the second command depends on the first.
Is there a way to do something like this.
find . -name '*.txt' -exec 'y=$(echo 1); echo $y' {} \;
...
And actually, I want to do this.
Run the find command, change to that directory that the file is in and then run the command on the file in the current directory.
find . -name '*.txt' -exec 'cd basedir && /mycmd/' {} \;
How do I do that?
find actually has a primary that switches to each file's directory and executes a command from there:
find . -name '*.txt' -execdir /mycmd {} \;
Find's -exec option expects an executable with arguments, not a command, but you can use bash -c cmd to run an arbitrary shell command like this:
find . -name '*.txt' -exec bash -c 'cd $(dirname {}) && pwd && /mycmd $(basename {})' \;
I have added pwd to confirm that mycmd executes in the right directory. You can remove it. dirname gives you the directory of each file and basename gives you the filename. If you omit basename your command will receive (as {}) pathname to each file relative to the directory where you run find which is different from mycmd's current directory due to cd, so mycmd will likely fail to find the file. If you want your command to receive absolute pathname, you can try this:
find $PWD -name '*.txt' -exec bash -c 'cd $(dirname {}) && pwd && /mycmd {}' \;

Resources