On a unix server, I'm trying to figure out how to remove a file, say "example.xls", from any subdirectories that start with v0 ("v0*").
I have tried something like:
find . -name "v0*" -type d -exec find . -name "example.xls" -type f
-exec rm {} \;
But i get errors. I have a solution but it works too well, i.e. it will delete the file in any subdirectory, regardless of it's name:
find . -type f -name "example.xls" -exec rm -f {} \;
Any ideas?
You will probably have to do it in two steps -- i.e. first find the directories, and then the files -- you can use xargs to make it in a single line, like
find . -name "v0*" -type d | \
xargs -l -I[] \
find [] -name "example.xls" -type f -exec rm {} \;
what it does, is first generating a list of viable directory name, and let xargs call the second find with the names locating the file name within that directory
Try:
find -path '*/v0*/example.xls' -delete
This matches only files named example.xls which, somewhere in its path, has a parent directory name that starts with v0.
Note that since find offers -delete as an action, it is not necessary to invoke the external executable rm.
Example
Consider this directory structure:
$ find .
.
./a
./a/example.xls
./a/v0
./a/v0/b
./a/v0/b/example.xls
./a/v0/example.xls
We can identify files example.xls who have one of their parent directories named v0*:
$ find -path '*/v0*/example.xls'
./a/v0/b/example.xls
./a/v0/example.xls
To delete those files:
find -path '*/v0*/example.xls' -delete
Alternative: find only those files directly under directory v0*
find -regex '.*/v0[^/]*/example.xls'
Using the above directory structure, this approach returns one file:
$ find -regex '.*/v0[^/]*/example.xls'
./a/v0/example.xls
To delete such files:
find -regex '.*/v0[^/]*/example.xls' -delete
Compatibility
Although my tests were performed with GNU find, both -regex and -path are required by POSIX and also supported by OSX.
Related
How can i search for example all .png files on an external disk and copy them to another directory?
Have tried to use the cp command. Have try it but don't work for me
?
Monterey 2.2.1
cp /Volumes/Data *.png /Volumes/Data/pictures_png
cp command won't work if you need to recursively copy from the sub directories. You need to use find.
Syntax:
find $SOURCE -type f -name '*.type' -exec cp '{}' $DESTINATION ';'
In your case,
find /Volumes/Data -type f -name '*.png' -exec cp '{}' /Volumes/Data/pictures_png ';'
Here is how it works:
-type f means copy only files not directories.
-name is to provide the filename to find. Here *.png for pattern matching
-exec executes the following line for each result the above find returns.
{} will be replaced with the results from find
; terminates -exec command
I'm learning bash scripting and needed some simple help.
Here is what I have thus far:
find . -type d -empty -not -path "./.git/*" -exec touch {}/.gitkeep \;
So what this does is starts from a root path, finds all directories inside this root path that are empty and do not have a .git folder, and then when that operation is successful it runs -exec touch {}/.gitkeep to create a file .gitkeep inside that empty directory to ensure proper git commits.
What I want now is to echo out the current file path for the gitkeep file just created.
My first question is:
Should I be piping | as so:
find . -type d -empty -not -path "./.git/*" -exec touch {}/.gitkeep | outputFilenameDisplayFunction \;
Or maybe repeat what -exec does as so:
find . -type d -empty -not -path "./.git/*" -exec touch {}/.gitkeep - exec outputFilenameDisplayFunction \;
Or maybe use >
find . -type d -empty -not -path "./.git/*" -exec touch {}/.gitkeep > outputFilenameDisplayFunction \;
None of these commands has been tested yet. I really am looking for explanations so i can be knowledgeable in the future.
As mentioned here, find accepts multiple -exec portions to the command.
In your case, the second one can call a script, as in here:
find . -type d -empty -not -path "./.git/*" -exec touch {}/.gitkeep \; -exec myscript {} \;
Note the \;.
The script would be:
#!/bin/sh
echo "$1" > "afile"
Charles Duffy actually proposes in the comments fir the second -exec:
-exec sh -c 'echo "$1" >>aFile' _ {} \;
avoid the need for an external file storing your script.
Let's start from your stated requirements:
So what this does is starts from a root path, finds all directories inside this root path that are empty and do not have a .git folder, and then when that operation is successful it runs -exec touch {}/.gitkeep to create a file .gitkeep inside that empty directory to ensure proper git commits.
If a directory is empty, it "can't have a .git folder" in the sense of having a child named .git by definition -- if it had any subdirectory, it wouldn't be empty. So we can completely ignore that part of your description in prose -- or interpret to refer to what the code actually appears to be intended to do, pruning any directory which is under .git.
Should that be your intent, -path is the wrong tool for that job altogether, as it still searches the .git tree (and then excludes all the things that it found); instead, use -prune to stop find from recursing down that path at all:
while IFS= read -r -d '' dirname; do
touch -- "${dirname}/.gitkeep"
printf '%q\n' "$dirname" # this goes to the logfile, since we open it for the whole loop
done < <(find . -name .git -prune -o -type d -empty -print0) >logFile
Why prefer this approach?
Instead of starting a shell per directory found (as would happen if you used -exec to start a shell script or a shell), it keeps your initial/primary shell running, and iterates through the loop once per item found.
Because it's running code in that shell, you can use shell functions; modify shell variables (as with (( ++directoriesFound )) to keep a counter, f/e), or perform redirections scoped to the loop (ie. >logFile) to open an output file just once and use if repeatedly within.
On GNU/anything, find has -printf, which makes doing what you want a straight
find -name .git -prune \
-o -type d -empty -printf %p/.gitkeep\\n -execdir touch {}/.gitkeep \;
(note: fixed omitted {}/, and GNU find's -execdir doesn't change the behavior here but is safer than -exec on systems that may find themselves under attack, the exec'd command is run directly in the location find got to rather than causing the executed command to re-walk the path).
Using
find . -name "*_develop-*"
I am able to find all folders in the current directory that contain _develop- in the folder name, e.g.
myfolder_develop-abcd
myfolder_develop-efgh
Now inside these found folders I'd like to delete the folder "temp".
How do I pipe the required command to look into the resulting folders, find the required folder and then delete it?
You can use -exec option to make a rm -rf command on match folder.
Command should be like that :
find . -name "*_develop-" -exec rm -rf {}/tmp/ \;
{} represent a match folder, so {}/tmp/ represent tmp folder inside a match folder
You could use -regex flag
find . -regex '.*_develop-.*/temp' -type d -delete
or if it is the folder directly below
find . -regex '.*_develop-[^/]*/temp' -type d -delete
Use find with xargs
find . -wholename "*_develop-*/temp" -print0 | xargs -0 rm -rf
Using print0 and xargs -0 replace spaces by a NULL character. It is useful if there is spaces in some directory names, so xargs would fail without it.
You could use find . -wholename "*_develop-*/temp" -delete but that will fail if temp directory is not empty.
I would like to delete old files from multiple directories but there is a wild card for one of the path attributes. So I'm trying to loop through each of those directories without specifying each one. I think I'm almost there but I'm not sure how to cd into the specific directory to delete the relevant files.
#! /bin/bash
DELETE_SEARCH_DIR=/apps/super/userprojects/elasticsearch/v131/node*/elasticsearch-1.3.1/logs/
for entry in `ls $DELETE_SEARCH_DIR`; do
find $path -name "*super*" -type f -mtime +10 -print -delete
#find . -type f -name $entry -exec rm -f {} \;
done
Any ideas on how to get into the specific directory and apply the delete?
find can search in multiple directories. You can do it like this:
DELETE_SEARCH_DIR=/apps/super/userprojects/elasticsearch/v131/node*/elasticsearch-1.3.1/logs
find $DELETE_SEARCH_DIR -type f -name '*super*' -mtime +10 -print -delete
I have many files without file extention. Now I want to add .txt to all files. I tried the following but it gives an error, mv: rename . to ..txt: Invalid argument.
How can I achieve this?
find . -iname "*.*" -exec bash -c 'mv "$0" "$0.txt"' {} \;
You're nearly there!
Just add -type f to only deal with files:
find . -type f -exec bash -c 'mv "$0" "$0.txt"' {} \;
If your mv handles the -n option, you might want to use it (that's the option to not overwrite existing files).
The error your having is because . is one of the first found by found, and your system complains (rightly) when you want to rename .! with the -type f you're sure this won't happen. Now if you wanted to act on everything inside your directory, you would, e.g., add -mindepth 1 at the beginning of the find command (as . is considered depth 0).
It is not very clear in your question, but what if you want to add the extension .txt to all files that don't have an extension? (we'll agree that to have an extension means to have a period in the name). In this case, you'll use the negation of -name '*.*' as follows:
find . -type f \! -name '*.*' -exec bash -c 'mv "$0" "$0.txt"' {} \;