How to clean up all maven projects with linux find command? - linux

I want to clean up all my maven projects at once. But I want to avoid to do it step-by-step going through all the folders manually and call mvn clean. So I thought to do this with the find command. I tried the following call:
find . -name pom.xml -exec mvn clean {} \;
The result was the error message: find: missing argument for "-exec".
Now my question: is it possible to do such a call with find and exec? I thought I can use every command as an argument for find -exec.
Thanks in advance
Hardie

If you expand what exec will run for you:
mvn clean dir1/dir2/pom.xml
You'll see that you treated the pom-file as a maven goal.
You should use -f flag, and apostrophes (to prevent globing):
find . -name pom.xml -exec mvn clean -f '{}' \;
As for me, I use this command
find . -name 'target' -a -type d -exec rm -rfv '{}' \;
This will delete all target folders.

I'm not sure what version of mvn command supports specifying pom file like that, but for e.g. Maven 3.x that doesn't work. We need a small change. Either run with -execdir which automatically changes the directory for us to the location of found pom file (and generally safer than -exec):
find . -name pom.xml -execdir mvn clean \;
or with specified alternate pom file option -f:
find . -name pom.xml -exec mvn clean -f '{}' \;

If you, like I, have quite a lot of maven projects checked out, the suggested approaches might take hours.
I currently have 700 projects checked out.
find . -maxdepth 3 -type f -name pom.xml |
sed 's|/pom.xml$|/target|' |
xargs -I{} bash -c '[ -d {} ] && echo {}' |
xargs -I{} bash -c 'mvn clean -f "$(sed "s|/target$|/pom.xml|" <<< "{}")"'
What I do here is I test that a target folder exist before I apply the clean command.
This makes it so that I do not activate maven unless there is reason to, an the execution time is then linear to the projects I've worked in recently, not the total number of projects.
You might need to fiddle a bit with the maxdepth as well, in case my number does not fit your bill.

Related

How to copy file after finding it

I am trying to create a script which will execute the following actions
1- execute mvn clean
2- execute mvn package
3- find the .jar file and move it to a destination folder passed as an arg on the command line
#!/bin/bash
destination=“$1”
#Clean
mvn clean
#Package
mvn package
#Transfer the generated jar file to the destination folder
find . -name “*.jar” | xargs cp -t $destination
This is running the maven scripts fine but when I go to the destination folder the file is not copied.
I know that the .jar file is there and is found when I print out the result of find . -name “*.jar”:
./target/QuoteTool-0.0.1-SNAPSHOT.jar
But the step of copying the file is not happening correctly.
Any help much appreciated.
Note: I am using a Mac
You don't need to use xargs but just use the -exec option within find command itself.
Also you have Unicode Windows style double-quotes (see Unicode Utilities), “*.jar” should have been used as "*.jar"
find . -type f -name "*.jar" -exec cp -t "$destination" "{}" +
In the above example, cp with + over exec will avoid forking a cp for each of the jar file found, but rather copies all the files found to destination in one shot.
I recommend to rewrite the line in the following way
find . -name "*.jar" -exec cp {} $destination \;
it gives you the result you would like to have
I would use the -e option with xargs to look something like
find . -name “*.jar” | xargs -e cp $destination/

Find files in a dir, executing a command with execdir and redirecting

It seems like I am unable to find a direct answer to this question.
I appreciate your help.
I'm trying to find all files with a specific name in a directory, read the last 1000 lines of the file and copy it in to a new file in the same directory. As an example:
Find all files names xyz.log in the current directory, copy the last 1000 lines to file abc.log (which doesn't exist).
I tried to use the following command with no luck:
find . -name "xyz.log" -execdir tail -1000 {} > abc.log \;
The problem I'm having is that for all the files in the current directory, they all write to abc.log in the CURRENT directory and not in the directory where xyz.log resides. Clearly the find with execdir is first executed and then the output is redirected to abc.log.
Can you guys suggest a way to fix this? I appreciate any information/help.
EDIT- I tried find . -name "xyz.log" -execdir sh -c "tail -1000 {} > abc.log" \; as suggested by some of the friends, but it gives me this error: sh: ./tail: No such file or directory error message. Do you guys have any idea what the problem is?
Luckily the solution to use -printf is working fine.
The simplest way is this:
find . -name "xyz.log" -execdir sh -c 'tail -1000 "{}" >abc.log' \;
A more flexible alternative is to first print out the commands and then execute them all with sh:
find . -name "xyz.log" -printf 'tail -1000 "%p" >"%h/abc.log"\n' | sh
You can remove the | sh from the end when you're trying it out/debugging.
There is a bug in some versions of findutils (4.2 and 4.3, though it was fixed in some 4.2.x and 4.3.x versions) that cause execdir arguments that contain {} to be prefixed with ./ (instead of the prefix being applied only to {} it is applied to the whole quoted string). To work around this you can use:
find . -name "xyz.log" -execdir sh -c 'tail -1000 "$1" >abc.log' sh {} \;
sh -c 'script' arg0 arg1 runs the sh script with arg0, arg1, etc. passed to it. By convention, arg0 is the name of the executable (here, "sh"). From the script you can access the arguments using $0 (corresponding to "sh"), $1 (corresponding to find's expansion of {}), etc.
The redirect isn't passed into execdir, so abc.log shows up in the directory you run the command in. -execdir also doesn't like embedded redirects. but you can workaround the problem by passing -execdir a shell command with a redirect embedded, like this:
find . -name "xyz.log" -execdir sh -c '/usr/bin/tail -1000 {} > abc.log' \;
Much credit to this blog post (not mine):
http://www.microhowto.info/howto/act_on_all_files_in_a_directory_tree_using_find.html
Edit
I put the full path to tail in the command (assuming it's in /usr/bin on your system), since sh may load a .profile with a PATH that differs from your current shell.
Here's another non-find (well, sorta - it still uses find but doesn't try to shoehorn find into doing the whole thing):
while read f
do
d=$(dirname "${f}")
tail -n 1000 "${f}" > "${d}/abc.log"
done < <(find . -type f -name xyz.log -print)

Running command recursively in linux

I'm trying to come up with a command that would run mp3gain FOLDER/SUBFOLDER/*.mp3 in each subfolder, but I'm having trouble understanding why this command doesn't work:
find . -type d -exec mp3gain \"{}\"/*.mp3 \;
When run, I get error Can't open "./FOLDER/SUBFOLDER"/*.mp3 for reading for each folder and subfolder.
If I run command manually with mp3gain "./FOLDER/SUBFOLDER"/*.mp3 it works. What's going wrong?
If you have a fixed data structure like
folder1/subfolder1/
folder1/subfolder2/
folder2/subfolder1/
[...]
and using zsh or bash version >=4.0 you could try
mp3gain **/*.mp3
But to make sure check the output of
ls **/*.mp3
before you are getting serious with mp3gain.
When you run mp3gain "./FOLDER/SUBFOLDER"/*.mp3 from your shell, the *.mp3 is getting expanded by the shell before being passed to mp3gain. When find runs it, there is no shell involved, and the *.mp3 is getting passed literally to mp3gain. The latter has no idea how to deal with wildcards (because normally it doesn't have to).
Hmmm. Just tried this to test how the directory is parsed by replacing mp3gain with echo and it works:
find . -type d -exec echo {}\/\*.mp3 \;
Try running your version of the command but with echo to see the file output for yourself:
find . -type d -exec echo \"{}\"/*.mp3 \;
Seems the quotes get in the way in your original command.
this works...
find /music -name *mp3 -exec mp3gain -r -k {} \;

Argument list too long error for rm, cp, mv commands

I have several hundred PDFs under a directory in UNIX. The names of the PDFs are really long (approx. 60 chars).
When I try to delete all PDFs together using the following command:
rm -f *.pdf
I get the following error:
/bin/rm: cannot execute [Argument list too long]
What is the solution to this error?
Does this error occur for mv and cp commands as well? If yes, how to solve for these commands?
The reason this occurs is because bash actually expands the asterisk to every matching file, producing a very long command line.
Try this:
find . -name "*.pdf" -print0 | xargs -0 rm
Warning: this is a recursive search and will find (and delete) files in subdirectories as well. Tack on -f to the rm command only if you are sure you don't want confirmation.
You can do the following to make the command non-recursive:
find . -maxdepth 1 -name "*.pdf" -print0 | xargs -0 rm
Another option is to use find's -delete flag:
find . -name "*.pdf" -delete
tl;dr
It's a kernel limitation on the size of the command line argument. Use a for loop instead.
Origin of problem
This is a system issue, related to execve and ARG_MAX constant. There is plenty of documentation about that (see man execve, debian's wiki, ARG_MAX details).
Basically, the expansion produce a command (with its parameters) that exceeds the ARG_MAX limit.
On kernel 2.6.23, the limit was set at 128 kB. This constant has been increased and you can get its value by executing:
getconf ARG_MAX
# 2097152 # on 3.5.0-40-generic
Solution: Using for Loop
Use a for loop as it's recommended on BashFAQ/095 and there is no limit except for RAM/memory space:
Dry run to ascertain it will delete what you expect:
for f in *.pdf; do echo rm "$f"; done
And execute it:
for f in *.pdf; do rm "$f"; done
Also this is a portable approach as glob have strong and consistant behavior among shells (part of POSIX spec).
Note: As noted by several comments, this is indeed slower but more maintainable as it can adapt more complex scenarios, e.g. where one want to do more than just one action.
Solution: Using find
If you insist, you can use find but really don't use xargs as it "is dangerous (broken, exploitable, etc.) when reading non-NUL-delimited input":
find . -maxdepth 1 -name '*.pdf' -delete
Using -maxdepth 1 ... -delete instead of -exec rm {} + allows find to simply execute the required system calls itself without using an external process, hence faster (thanks to #chepner comment).
References
I'm getting "Argument list too long". How can I process a large list in chunks? # wooledge
execve(2) - Linux man page (search for ARG_MAX) ;
Error: Argument list too long # Debian's wiki ;
Why do I get “/bin/sh: Argument list too long” when passing quoted arguments? # SuperUser
find has a -delete action:
find . -maxdepth 1 -name '*.pdf' -delete
Another answer is to force xargs to process the commands in batches. For instance to delete the files 100 at a time, cd into the directory and run this:
echo *.pdf | xargs -n 100 rm
If you’re trying to delete a very large number of files at one time (I deleted a directory with 485,000+ today), you will probably run into this error:
/bin/rm: Argument list too long.
The problem is that when you type something like rm -rf *, the * is replaced with a list of every matching file, like “rm -rf file1 file2 file3 file4” and so on. There is a relatively small buffer of memory allocated to storing this list of arguments and if it is filled up, the shell will not execute the program.
To get around this problem, a lot of people will use the find command to find every file and pass them one-by-one to the “rm” command like this:
find . -type f -exec rm -v {} \;
My problem is that I needed to delete 500,000 files and it was taking way too long.
I stumbled upon a much faster way of deleting files – the “find” command has a “-delete” flag built right in! Here’s what I ended up using:
find . -type f -delete
Using this method, I was deleting files at a rate of about 2000 files/second – much faster!
You can also show the filenames as you’re deleting them:
find . -type f -print -delete
…or even show how many files will be deleted, then time how long it takes to delete them:
root#devel# ls -1 | wc -l && time find . -type f -delete
100000
real 0m3.660s
user 0m0.036s
sys 0m0.552s
Or you can try:
find . -name '*.pdf' -exec rm -f {} \;
you can try this:
for f in *.pdf
do
rm "$f"
done
EDIT:
ThiefMaster comment suggest me not to disclose such dangerous practice to young shell's jedis, so I'll add a more "safer" version (for the sake of preserving things when someone has a "-rf . ..pdf" file)
echo "# Whooooo" > /tmp/dummy.sh
for f in '*.pdf'
do
echo "rm -i \"$f\""
done >> /tmp/dummy.sh
After running the above, just open the /tmp/dummy.sh file in your favorite editor and check every single line for dangerous filenames, commenting them out if found.
Then copy the dummy.sh script in your working dir and run it.
All this for security reasons.
For somone who doesn't have time.
Run the following command on terminal.
ulimit -S -s unlimited
Then perform cp/mv/rm operation.
I'm surprised there are no ulimit answers here. Every time I have this problem I end up here or here. I understand this solution has limitations but ulimit -s 65536 seems to often do the trick for me.
You could use a bash array:
files=(*.pdf)
for((I=0;I<${#files[#]};I+=1000)); do
rm -f "${files[#]:I:1000}"
done
This way it will erase in batches of 1000 files per step.
you can use this commend
find -name "*.pdf" -delete
The rm command has a limitation of files which you can remove simultaneous.
One possibility you can remove them using multiple times the rm command bases on your file patterns, like:
rm -f A*.pdf
rm -f B*.pdf
rm -f C*.pdf
...
rm -f *.pdf
You can also remove them through the find command:
find . -name "*.pdf" -exec rm {} \;
If they are filenames with spaces or special characters, use:
find -name "*.pdf" -delete
For files in current directory only:
find -maxdepth 1 -name '*.pdf' -delete
This sentence search all files in the current directory (-maxdepth 1) with extension pdf (-name '*.pdf'), and then, delete.
i was facing same problem while copying form source directory to destination
source directory had files ~3 lakcs
i used cp with option -r and it's worked for me
cp -r abc/ def/
it will copy all files from abc to def without giving warning of Argument list too long
Try this also If you wanna delete above 30/90 days (+) or else below 30/90(-) days files/folders then you can use the below ex commands
Ex: For 90days excludes above after 90days files/folders deletes, it means 91,92....100 days
find <path> -type f -mtime +90 -exec rm -rf {} \;
Ex: For only latest 30days files that you wanna delete then use the below command (-)
find <path> -type f -mtime -30 -exec rm -rf {} \;
If you wanna giz the files for more than 2 days files
find <path> -type f -mtime +2 -exec gzip {} \;
If you wanna see the files/folders only from past one month .
Ex:
find <path> -type f -mtime -30 -exec ls -lrt {} \;
Above 30days more only then list the files/folders
Ex:
find <path> -type f -mtime +30 -exec ls -lrt {} \;
find /opt/app/logs -type f -mtime +30 -exec ls -lrt {} \;
And another one:
cd /path/to/pdf
printf "%s\0" *.[Pp][Dd][Ff] | xargs -0 rm
printf is a shell builtin, and as far as I know it's always been as such. Now given that printf is not a shell command (but a builtin), it's not subject to "argument list too long ..." fatal error.
So we can safely use it with shell globbing patterns such as *.[Pp][Dd][Ff], then we pipe its output to remove (rm) command, through xargs, which makes sure it fits enough file names in the command line so as not to fail the rm command, which is a shell command.
The \0 in printf serves as a null separator for the file names wich are then processed by xargs command, using it (-0) as a separator, so rm does not fail when there are white spaces or other special characters in the file names.
Argument list too long
As this question title for cp, mv and rm, but answer stand mostly for rm.
Un*x commands
Read carefully command's man page!
For cp and mv, there is a -t switch, for target:
find . -type f -name '*.pdf' -exec cp -ait "/path to target" {} +
and
find . -type f -name '*.pdf' -exec mv -t "/path to target" {} +
Script way
There is an overall workaroung used in bash script:
#!/bin/bash
folder=( "/path to folder" "/path to anther folder" )
if [ "$1" != "--run" ] ;then
exec find "${folder[#]}" -type f -name '*.pdf' -exec $0 --run {} +
exit 0;
fi
shift
for file ;do
printf "Doing something with '%s'.\n" "$file"
done
What about a shorter and more reliable one?
for i in **/*.pdf; do rm "$i"; done
I had the same problem with a folder full of temporary images that was growing day by day and this command helped me to clear the folder
find . -name "*.png" -mtime +50 -exec rm {} \;
The difference with the other commands is the mtime parameter that will take only the files older than X days (in the example 50 days)
Using that multiple times, decreasing on every execution the day range, I was able to remove all the unnecessary files
You can create a temp folder, move all the files and sub-folders you want to keep into the temp folder then delete the old folder and rename the temp folder to the old folder try this example until you are confident to do it live:
mkdir testit
cd testit
mkdir big_folder tmp_folder
touch big_folder/file1.pdf
touch big_folder/file2.pdf
mv big_folder/file1,pdf tmp_folder/
rm -r big_folder
mv tmp_folder big_folder
the rm -r big_folder will remove all files in the big_folder no matter how many. You just have to be super careful you first have all the files/folders you want to keep, in this case it was file1.pdf
To delete all *.pdf in a directory /path/to/dir_with_pdf_files/
mkdir empty_dir # Create temp empty dir
rsync -avh --delete --include '*.pdf' empty_dir/ /path/to/dir_with_pdf_files/
To delete specific files via rsync using wildcard is probably the fastest solution in case you've millions of files. And it will take care of error you're getting.
(Optional Step): DRY RUN. To check what will be deleted without deleting. `
rsync -avhn --delete --include '*.pdf' empty_dir/ /path/to/dir_with_pdf_files/
.
.
.
Click rsync tips and tricks for more rsync hacks
I found that for extremely large lists of files (>1e6), these answers were too slow. Here is a solution using parallel processing in python. I know, I know, this isn't linux... but nothing else here worked.
(This saved me hours)
# delete files
import os as os
import glob
import multiprocessing as mp
directory = r'your/directory'
os.chdir(directory)
files_names = [i for i in glob.glob('*.{}'.format('pdf'))]
# report errors from pool
def callback_error(result):
print('error', result)
# delete file using system command
def delete_files(file_name):
os.system('rm -rf ' + file_name)
pool = mp.Pool(12)
# or use pool = mp.Pool(mp.cpu_count())
if __name__ == '__main__':
for file_name in files_names:
print(file_name)
pool.apply_async(delete_files,[file_name], error_callback=callback_error)
If you want to remove both files and directories, you can use something like:
echo /path/* | xargs rm -rf
I only know a way around this.
The idea is to export that list of pdf files you have into a file. Then split that file into several parts. Then remove pdf files listed in each part.
ls | grep .pdf > list.txt
wc -l list.txt
wc -l is to count how many line the list.txt contains. When you have the idea of how long it is, you can decide to split it in half, forth or something. Using split -l command
For example, split it in 600 lines each.
split -l 600 list.txt
this will create a few file named xaa,xab,xac and so on depends on how you split it.
Now to "import" each list in those file into command rm, use this:
rm $(<xaa)
rm $(<xab)
rm $(<xac)
Sorry for my bad english.
I ran into this problem a few times. Many of the solutions will run the rm command for each individual file that needs to be deleted. This is very inefficient:
find . -name "*.pdf" -print0 | xargs -0 rm -rf
I ended up writing a python script to delete the files based on the first 4 characters in the file-name:
import os
filedir = '/tmp/' #The directory you wish to run rm on
filelist = (os.listdir(filedir)) #gets listing of all files in the specified dir
newlist = [] #Makes a blank list named newlist
for i in filelist:
if str((i)[:4]) not in newlist: #This makes sure that the elements are unique for newlist
newlist.append((i)[:4]) #This takes only the first 4 charcters of the folder/filename and appends it to newlist
for i in newlist:
if 'tmp' in i: #If statment to look for tmp in the filename/dirname
print ('Running command rm -rf '+str(filedir)+str(i)+'* : File Count: '+str(len(os.listdir(filedir)))) #Prints the command to be run and a total file count
os.system('rm -rf '+str(filedir)+str(i)+'*') #Actual shell command
print ('DONE')
This worked very well for me. I was able to clear out over 2 million temp files in a folder in about 15 minutes. I commented the tar out of the little bit of code so anyone with minimal to no python knowledge can manipulate this code.
I have faced a similar problem when there were millions of useless log files created by an application which filled up all inodes. I resorted to "locate", got all the files "located"d into a text file and then removed them one by one. Took a while but did the job!
I solved with for
I am on macOS with zsh
I moved thousands only jpg files. Within mv in one line command.
Be sure there are no spaces or special characters in the name of the files you are trying to move
for i in $(find ~/old -type f -name "*.jpg"); do mv $i ~/new; done
A bit safer version than using xargs, also not recursive:
ls -p | grep -v '/$' | grep '\.pdf$' | while read file; do rm "$file"; done
Filtering our directories here is a bit unnecessary as 'rm' won't delete it anyway, and it can be removed for simplicity, but why run something that will definitely return error?
Using GNU parallel (sudo apt install parallel) is super easy
It runs the commands multithreaded where '{}' is the argument passed
E.g.
ls /tmp/myfiles* | parallel 'rm {}'
For remove first 100 files:
rm -rf 'ls | head -100'

How to remove all .svn directories from my application directories

One of the missions of an export tool I have in my application, is to clean all .svn directories from my application directory tree. I am looking for a recursive command in the Linux shell that will traverse the entire tree and delete the .svn files.
I am not using export, as this script will be used for some other file/directory names which are not related to SVN. I tried something like:
find . -name .svn | rm -fr
It didn't work...
Try this:
find . -name .svn -exec rm -rf '{}' \;
Before running a command like that, I often like to run this first:
find . -name .svn -exec ls '{}' \;
What you wrote sends a list of newline separated file names (and paths) to rm, but rm doesn't know what to do with that input. It's only expecting command line parameters.
xargs takes input, usually separated by newlines, and places them on the command line, so adding xargs makes what you had work:
find . -name .svn | xargs rm -fr
xargs is intelligent enough that it will only pass as many arguments to rm as it can accept. Thus, if you had a million files, it might run rm 1,000,000/65,000 times (if your shell could accept 65,002 arguments on the command line {65k files + 1 for rm + 1 for -fr}).
As persons have adeptly pointed out, the following also work:
find . -name .svn -exec rm -rf {} \;
find . -depth -name .svn -exec rm -fr {} \;
find . -type d -name .svn -print0|xargs -0 rm -rf
The first two -exec forms both call rm for each folder being deleted, so if you had 1,000,000 folders, rm would be invoked 1,000,000 times. This is certainly less than ideal. Newer implementations of rm allow you to conclude the command with a + indicating that rm will accept as many arguments as possible:
find . -name .svn -exec rm -rf {} +
The last find/xargs version uses print0, which makes find generate output that uses \0 as a terminator rather than a newline. Since POSIX systems allow any character but \0 in the filename, this is truly the safest way to make sure that the arguments are correctly passed to rm or the application being executed.
In addition, there's a -execdir that will execute rm from the directory in which the file was found, rather than at the base directory and a -depth that will start depth first.
No need for pipes, xargs, exec, or anything:
find . -name .svn -delete
Edit: Just kidding, evidently -delete calls unlinkat() under the hood, so it behaves like unlink or rmdir and will refuse to operate on directories containing files.
There are already many answers provided for deleting the .svn-directory. But I want to add, that you can avoid these directories from the beginning, if you do an export instead of a checkout:
svn export <url>
If you don't like to see a lot of
find: `./.svn': No such file or directory
warnings, then use the -depth switch:
find . -depth -name .svn -exec rm -fr {} \;
In Windows, you can use the following registry script to add "Delete SVN Folders" to your right click context menu. Run it on any directory containing those pesky files.
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\DeleteSVN]
#="Delete SVN Folders"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\DeleteSVN\command]
#="cmd.exe /c \"TITLE Removing SVN Folders in %1 && COLOR 9A && FOR /r \"%1\" %%f IN (.svn) DO RD /s /q \"%%f\" \""
You almost had it. If you want to pass the output of a command as parameters to another one, you'll need to use xargs. Adding -print0 makes sure the script can handle paths with whitespace:
find . -type d -name .svn -print0|xargs -0 rm -rf
find . -name .svn |xargs rm -rf
As an important issue, when you want to utilize shell to delete .svn folders You need -depth argument to prevent find command entering the directory that was just deleted and showing error messages like e.g.
"find: ./.svn: No such file or directory"
As a result, You can use find command like below:
cd [dir_to_delete_svn_folders]
find . -depth -name .svn -exec rm -fr {} \;
Try this:
find . -name .svn -exec rm -v {} \;
Read more about the find command at developerWorks.
Alternatively, if you want to export a copy without modifying the working copy, you can use rsync:
rsync -a --exclude .svn path/to/working/copy path/to/export

Resources