How can I move many files without having Argument list too long? - linux

I am trying to move about 700,000 .jpg files from one directory to another in my Ubuntu server. I tried the following:
xargs mv * -t /var/www/html/
and
echo (*.jpg|*.png|*.bmp) | xargs mv -t /var/www/html/
and
echo (*.jpg) | xargs mv -t /var/www/html/
and
find . -name "*.jpg" -print0 | xargs mv * ../
and they all give me the same error: /usr/bin/xargs: Argument list too long
what should I do? Please help me out. Thanks :)

If you use find I would recommend you to use the -exec attribute. So your result should be find . -name "*.jpg" -exec mv {} /home/new/location \;.
However I would recommend to check what the find command returns you, replacing the exec part with: -exec ls -lrt {} \;

Try:
find /path/to/old-directory -type f | xargs -i mv "{}" /path/to/new-directory

You could have tried:
for f in *.jpg do;
mv -tv $f /var/www/html/
done
for f in *.png do;
mv -tv $f /var/www/html/
done
for f in *.bmp do;
mv -tv $f /var/www/html/
done
also, you should carefully read xargs(1); I strongly suspect that
find . -name "*.jpg" -print0 | xargs -n 1000 -I '{}' mv '{}' ../
should work for you
At last, learn more about rename(1). It is probably enough for the job.

Related

Remove all files contain specific string - Bash

I have these bad data
AWS-Console.pngcrop-AWS-Console.png
Alimofire.pngcrop-Alimofire.png
Amazon-ECR-.pngcrop-Amazon-ECR-.png
Amazon-ECS.pngcrop-Amazon-ECS.png
Amazon-RDS.pngcrop-Amazon-RDS.png
Angular.pngcrop-Angular.png
AngularJS.pngcrop-AngularJS.png
.... 1000 more
I'm trying to delete them
I've tried
ls public/assets/fe/img/skill/ | grep crop | rm -rf *crop*
ls public/assets/fe/img/skill/ | grep crop | rm -rf
rm -rf $(ls public/assets/fe/img/skill/ | grep crop)
None of them work ...
rm can handle the glob expressions that ls handles:
rm public/assets/fe/img/skill/*crop*
Use the find command instead
find . -name "*crop*" -type f -exec rm -i {} \;
-type f will specify to search file only and avoid directories
-exec requires the command input to end with \;, the {} being substitute by the result of the command
the -i will ask you to confirm ; remove it once sure what you do.
advice display the result beforehand with -print in place of -exec ...
find . -name "*crop*" -type f -print
More here where your question would find more accurate answers
The main problem in your commands is the missing path in the output of the ls command.
ls public/assets/fe/img/skill/ | grep crop will retur e.g. AWS-Console.pngcrop-AWS-Console.png which is passed to rm. But rm AWS-Console.pngcrop-AWS-Console.png fails because there is no such file in the current directory. It should be rm public/assets/fe/img/skill/AWS-Console.pngcrop-AWS-Console.png instead.
Adding -d to the ls command should do the trick:
ls -d public/assets/fe/img/skill/ | grep crop | rm -rf
rm -rf $(ls -d public/assets/fe/img/skill/ | grep crop)
As pointed out in other answers, other solutions exist, including:
rm public/assets/fe/img/skill/*crop*
find public/assets/fe/img/skill/ -name "*crop*" -type f -exec rm -i {} \;
If it's a really large number of files (apparently wasn't in your case), xargs can speed up the process. This applies for a lot of things you might want to read from a pipe.
find . -name "*crop*" -type f | xargs rm
The main advantage of using find here is that it's an easy way to ignore directories. If that's not an issue, let the OS handle all that.
printf "%s\n" public/assets/fe/img/skill/*crop* | xargs rm
If you need to be able to pick up files in subdirectories -
shopt -s globstar # double asterisks not include arbitrary preceding paths
printf "%s\n" public/assets/fe/img/skill/**crop* | xargs rm
You might want to look over the list first, though.
printf "%s\n" public/assets/fe/img/skill/*crop* >crop.lst
# check the list - vi, grep, whatever satisfies you.
xargs rm < crop.lst # fast-delete them in bulk

Removing files with rm using find and xargs

When I do
rm file.txt
or
rm *.txt
I'm prompted for each file, since I didn't specify the -f option to rm.
But when I do this:
find . -type f -name '*.txt' | xargs rm
the files are removed without the confirmation.
What is the logics behind this? Is it possible to find the reason in some documentation? I cannot explain why this would be the case.
You have an alias set for the rm command to 'rm -i'. Therefore if you invoke the command directly as in
rm file.txt
or
rm *.txt
the alias will be expanded. If you will call it with xargs as in
find . -type f -name '*.txt' | xargs rm
The rm is passed as a simple string argument to xargs and is later invoked by xargs without alias substitution of the shell.
You alias is probably defined in ~/.bashrc, in case you want to remove it.
you can use this simple command to solve your problem
find . -type f -name '*.txt' -delete
Depending on your version of xargs you may have the --no-run-if-empty GNU extension option available to you:
find . -type f -name '*.txt' | xargs --no-run-if-empty rm -rf

how to find and copy files in a sub directory from parent directory linux

I have several parent folders like GJ1, GJ2 etc. Each of these folders contain three images like GJ11_F.jpg, GJ11_P.jpg. I need to only display all the GJ11_F.jpg files including their respective parent directories.
find . -type f -name "*_F.jpg" | xargs cp -t ~/home/ubuntu/
but the above command will only copy the *_F.jpg files and not their respective parent directories GJ1.
Is xargs not the one im supposed to try?
I have also tried -
find . -name "*_F.jpg" -exec sh -c 'rsync -a "${0%/*}" ~/home/ubuntu/' {} \;
One easy way is to use tar which will deal with the directories automatically:
find . -type f -name "*_F.jpg" -print0 | tar c --null -T - | tar xC ~/home/ubuntu/
And here's a solution with a while loop:
find . -type f -name "*_F.jpg" -print0 |
while IFS= read -r -d '' file; do
mkdir -p ~/home/ubuntu/"$(dirname -- "$file")"
cp -ai -- "$file" ~/home/ubuntu/"$file"
done

How to pipe the results of 'find' to mv in Linux

How do I pipe the results of a 'find' (in Linux) to be moved to a different directory? This is what I have so far.
find ./ -name '*article*' | mv ../backup
but its not yet right (I get an error missing file argument, because I didn't specify a file, because I was trying to get it from the pipe)
find ./ -name '*article*' -exec mv {} ../backup \;
OR
find ./ -name '*article*' | xargs -I '{}' mv {} ../backup
xargs is commonly used for this, and mv on Linux has a -t option to facilitate that.
find ./ -name '*article*' | xargs mv -t ../backup
If your find supports -exec ... \+ you could equivalently do
find ./ -name '*article*' -exec mv -t ../backup {} \+
The -t option is a GNU extension, so it is not portable to systems which do not have GNU coreutils (though every proper Linux I have seen has that, with the possible exception of Busybox). For complete POSIX portability, it's of course possible to roll your own replacement, maybe something like
find ./ -name '*article*' -exec sh -c 'mv "$#" "$0"' ../backup {} \+
where we shamelessly abuse the convenient fact that the first argument after sh -c 'commands' ends up as the "script name" parameter in $0 so that we don't even need to shift it.
Probably see also https://mywiki.wooledge.org/BashFAQ/020
I found this really useful having thousands of files in one folder:
ls -U | head -10000 | egrep '\.png$' | xargs -I '{}' mv {} ./png
To move all pngs in first 10000 files to subfolder png
mv $(find . -name '*article*') ../backup
Here are a few solutions.
find . -type f -newermt "2019-01-01" ! -newermt "2019-05-01" \
-exec mv {} path \;**
or
find path -type f -newermt "2019-01-01" ! -newermt "2019-05-01" \
-exec mv {} path \;
or
find /Directory/filebox/ -type f -newermt "2019-01-01" \
! -newermt "2019-05-01" -exec mv {} ../filemove/ \;
The backslash + newline is just for legibility; you can equivalently use a single long line.
xargs is your buddy here (When you have multiple actions to take)!
And using it the way I have shown will give great control to you as well.
find ./ -name '*article*' | xargs -n1 sh -c "mv {} <path/to/target/directory>"
Explanation:
-n1
Number of lines to consider for each operation ahead
sh -c
The shell command to execute giving it the lines as per previous condition
"mv {} /target/path"
The move command will take two arguments-
1) The line(s) from operation 1, i.e. {}, value substitutes automatically
2) The target path for move command, as specified
Note: the "Double Quotes" are specified to allow any number of spaces or arguments for the shell command which receives arguments from xargs

moving files recurisively on linux

find ./dir -type f -iname "*.t[argz]*[bz2]" -print | xargs mv --target-directory=dir
seems to fail on file that has spaces in the name.
how to improve it? or alternative?
thanks for answer below: my mv doesn't support --null or -0, I'm using cygwin:
$ mv --help
Usage: mv [OPTION]... [-T] SOURCE DEST
or: mv [OPTION]... SOURCE... DIRECTORY
or: mv [OPTION]... -t DIRECTORY SOURCE...
Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.
Mandatory arguments t
.
Use -print0 instead of -print on the find command, and the xargs -0 (or --null) option - then NULs will be used as separators rather than newlines and spaces.
find ./dir -type f -iname "*.t[argz]*[bz2]" -print0 | xargs --null mv --target-directory=dir
Have you looked at the -exec option for find?
find ./dir -type f -iname "*.t[argz][bz2]" -exec mv {} --target-directory=dir ';'
The -exec option will execute any options following it as a command until it sees the semi-colon wrapped in quotes. This way you won't have to deal with the spacing issue.
GNU find
find ./dir -type f -iname "*.t[argz]*[bz2]" -exec mv "{}" /destination +;

Resources