linux recursive copy specified files doesn't work - regex

I want to recursive copy all the files which start with letters in directory data to directory test. So I wrote this:
find data -type f -exec grep '^[a-z]' {} \; -exec cp -f {} ./test \;
However, it also matched other files.
What's wrong with the code?

Your command isn't executing grep on filenames, but rather on the contents of those files.
You say:
copy all the files which start with letters in directory
which would use a find command that's matching filenames which requires the -name option. For example,
find data -type f -name '[a-z]*'
By using the -exec option to find, instead you're executing the provided command (grep '^[a-z]' {}) on every file that find finds in the data directory since there is no filename matching clause (-name).
The command you likely want is:
find data -type f -name '[a-z]*' -exec cp -f {} ./test \;

Related

Find and delete all core files in a directory

Core files are generated when a program terminates abnormally. It consists the working memory of the system when the program exits abnormally. You can use a debugger with the generated core file to debug the program. The Challenge is:
Delete all core files from a directory (recursive search). Core files are quite huge in size and you may want to delete them to save memory
Make sure you don't delete any folder named core and some other filed named core which not actually a memory/system dump
After some searching on the internet, I found a nice piece of code to do this. Drawback is it asks you to recognize the core file to make sure its not some other file named core. Source : http://csnbbs.com/
Code:
find . -name core\* -user $USER -type f -size +1000000c -exec file {} \; -exec ls -l {} \; -exec printf "\n\ny to remove this core file\n" \; -exec /bin/rm -i {} \;
Please post if you have better solutions.
To delete all files matching to the regex "*.core" you can use:
find . -name "*.core" -type f -delete
find supports many filters like:
-size +1000000c # size > 1G
-user $USER # specific user
-mtime +3 # older than 3 days
if you are afraid for files ending with "core" that are not core files you can filter by file command piped to some other linux commands. for example -
find . -name "*.core" -type f -exec file {} \; | grep 'core file' | awk -F":" '{print $1}' | xargs -n1 -P4 rm -rf

Recursively change file extensions in Bash

I want to recursively iterate through a directory and change the extension of all files of a certain extension, say .t1 to .t2. What is the bash command for doing this?
Use:
find . -name "*.t1" -exec bash -c 'mv "$1" "${1%.t1}".t2' - '{}' +
If you have rename available then use one of these:
find . -name '*.t1' -exec rename .t1 .t2 {} +
find . -name "*.t1" -exec rename 's/\.t1$/.t2/' '{}' +
None of the suggested solutions worked for me on a fresh install of debian 14.
This should work on any Posix/MacOS
find ./ -depth -name "*.t1" -exec sh -c 'mv "$1" "${1%.t1}.t2"' _ {} \;
All credits to:
https://askubuntu.com/questions/35922/how-do-i-change-extension-of-multiple-files-recursively-from-the-command-line
If your version of bash supports the globstar option (version 4 or later):
shopt -s globstar
for f in **/*.t1; do
mv "$f" "${f%.t1}.t2"
done
I would do this way in bash :
for i in $(ls *.t1);
do
mv "$i" "${i%.t1}.t2"
done
EDIT :
my mistake : it's not recursive, here is my way for recursive changing filename :
for i in $(find `pwd` -name "*.t1");
do
mv "$i" "${i%.t1}.t2"
done
Or you can simply install the mmv command and do:
mmv '*.t1' '#1.t2'
Here #1 is the first glob part i.e. the * in *.t1 .
Or in pure bash stuff, a simple way would be:
for f in *.t1; do
mv "$f" "${f%.t1}.t2"
done
(i.e.: for can list files without the help of an external command such as ls or find)
HTH
My lazy copy-pasting of one of these solutions didn't work, but I already had fd-find installed, so I used that:
fd --extension t1 --exec mv {} {.}.t2
From fd's manpage, when executing a command (using --exec):
The following placeholders are substituted by a
path derived from the current search result:
{} path
{/} basename
{//} parent directory
{.} path without file extension
{/.} basename without file extension

How to use find and the prune option with an while loop

i've got an question about find, prune and print combined with an while loop. I want find every file named trace but not the files ending on mailed. Also i want to exclude the files in the lost+found directory. My idea was to use the following command:
find /opt/myTESTdir/ -iwholename '*lost+found' -prune -o -ctime +4 -type f -iname "*trace*" -not -iname "*.mailed*" -print0 | while read file ; do newfile=${file%.txt}".mailed" ; mv -v $file $newfile ; done
My question is now should this work or is there an syntax error? I've tried out the find command without everything behind the pipe and it seems, that's work correctly. But i'm not sure about the combination. I hope you could answer me :)
(Sorry for my bad english)
In while loop, it seems you are trying to rename files with extension .txt to .mailed. You can achieve the same using -exec option.
Try adding following portion to the end of your find command and remove piping to while loop.
-exec sh -c 'mv -f $0 ${0%.txt}.mailed' {} \;
Complete command would look like
find /opt/myTESTdir/ -iwholename '*lost+found' -prune -o -ctime +4 -type f -iname '*trace*' ! -iname '*.mailed*' -exec sh -c 'mv -f $0 ${0%.txt}.mailed' {} \;

Unix find with wildcard directory structure

I am trying to do a find where I can specify wildcards in the directory structure then do a grep for www.domain.com in all the files within the data directory.
ie
find /a/b/c/*/WA/*/temp/*/*/data -type f -exec grep -l "www.domain.com" {} /dev/null \;
This works fine where there is only one possible level between c/*/WA.
How would I go about doing the same thing above where there could be multiple levels between C/*/WA?
So it could be at
/a/b/c/*/*/WA/*/temp/*/*/data
or
/a/b/c/*/*/*/WA/*/temp/*/*/data
There is no defined number of directories between /c/ and /WA/; there could be multiple levels and at each level there could be the /WA/*/temp/*/*/data.
Any ideas on how to do a find such as that?
How about using a for loop to find the WA directories, then go from there:
for DIR in $(find /a/b/c -type d -name WA -print); do
find $DIR/*/temp/*/*/data -type f \
-exec grep -l "www.domain.com" {} /dev/null \;
done
You may be able to get all that in a single command, but I think clarity is more important in the long run.
Assuming no spaces in the paths, then I'd think in terms of:
find /a/b/c -name data -type f |
grep -E '/WA/[^/]+/temp/[^/]+/[^/]+/data' |
xargs grep -l "www.domain.com" /dev/null
This uses find to find the files (rather than making the shell do most of the work), then uses the grep -E (equivalent to egrep) to select the names with the correct pattern in the path, and then uses xargs and grep (again) to find the target pattern.

remove files when name does NOT contain some words

I am using Linux and intend to remove some files using shell.
I have some files in my folder, some filenames contain the word "good", others don't.
For example:
ssgood.wmv
ssbad.wmv
goodboy.wmv
cuteboy.wmv
I want to remove the files that does NOT contain "good" in the name, so the remaining files are:
ssgood.wmv
goodboy.wmv
How to do that using rm in shell? I try to use
rm -f *[!good].*
but it doesn't work.
Thanks a lot!
This command should do what you you need:
ls -1 | grep -v 'good' | xargs rm -f
It will probably run faster than other commands, since it does not involve the use of a regex (which is slow, and unnecessary for such a simple operation).
With bash, you can get "negative" matching via the extglob shell option:
shopt -s extglob
rm !(*good*)
You can use find with the -not operator:
find . -not -iname "*good*" -a -not -name "." -exec rm {} \;
I've used -exec to call rm there, but I wonder if find has a built-in delete action it does, see below.
But very careful with that. Note in the above I've had to put an -a -not -name "." clause in, because otherwise it matched ., the current directory. So I'd test thoroughly with -print before putting in the -exec rm {} \; bit!
Update: Yup, I've never used it, but there is indeed a -delete action. So:
find . -not -iname "*good*" -a -not -name "." -delete
Again, be careful and double-check you're not matching more than you want to match first.