About the usage of linux command "xargs" - linux

I have some file like
love.txt
loveyou.txt
in directory useful; I want to copy this file to directory /tmp.
I use this command:
find ./useful/ -name "love*" | xargs cp /tmp/
but is doesn't work, just says:
cp: target `./useful/loveyou.txt' is not a directory
when I use this command:
find ./useful/ -name "love*" | xargs -i cp {} /tmp/
it works fine,
I want to know why the second works, and more about the usage of -i cp {}.

xargs puts the words coming from the standard input to the end of the argument list of the given command. The first form therefore creates
cp /tmp/ ./useful/love.txt ./useful/loveyou.txt
Which does not work, because there are more than 2 arguments and the last one is not a directory.
The -i option tells xargs to process one file at a time, though, replacing {} with its name, so it is equivalent to
cp ./useful/love.txt /tmp/
cp ./useful/loveyou.txt /tmp/
Which clearly works well.

When using the xargs -i command, {} is substituted with each element you find. So, in your case, for both "loveyou.txt" and "love.txt", the following command will be run:
cp ./useful/loveyou.txt /tmp/
cp ./useful/love.txt /tmp/
if you omit the {}, all the elements you find will automatically be inserted at the end of the command, so, you will execute the nonsensical command:
cp /tmp/ ./useful/loveyou.txt ./useful/love.txt

xargs appends the values fed in as a stream to the end of the command - it does not run the command once per input value. If you want the same command run multiple times - that is what the -i cp {} syntax is for.
This works well for commands which accept a list of arguments at the end (e.g. grep) - unfortunately cp is not one of those - it considers the arguments you pass in as directories to copy to, which explains the 'is not a directory' error.

The first example will do this:
cp /tmp/ love.txt loveyou.txt
Which can't be done, since they attempt to copy the directory /tmp and the file love.txt to the file loveyou.txt.
In the second example, -i tells xargs to replace every instance of {} with the argument, so it will do:
cp love.txt /tmp/
cp loveyou.txt /tmp/

find ./useful/ -name "love*" | xargs cp -t /tmp/

You might avoid xargs that way:
find ./useful/ -name "love*" -exec sh -c 'cp "$#" /tmp' sh {} +

Related

Move command error target is not directory UNIX

I am new to unix and I am trying to write a bash that moves all files that ends to .c to another folder. When I'm executing command:
find ~/testfiles -name '*.c' -exec mv -i ~/destination {} + I got an error: mv: target '/home/user/testfiles/dir3/sourcefile1.c' is not a directory.
The testfiles folder includes files and folders.
I am using ubuntu 20.04 if that helps..Thanks for your time.
In find -exec the {} is replaced by the file/directory found.
The mv command used as in your example is trying to move the first argument to the second argument so your arguments are in the wrong order.
This would be correct:
[[ -d ~/destination ]] && find ~/testfiles -name '*.c' -exec mv -i '{}' ~/destination \;
A slightly more effetive way would be to use mvs -t (target) option which means that you can use the + in find (to supply as many arguments to mv in one go as possible):
[[ -d ~/destination ]] && find ~/testfiles -name '*.c' -exec mv -i -t ~/destination '{}' +
I added [[ -d ~/destination ]] && before the find command since one common mistake is to move a lot of files to the same destination and if it doesn't exist (or is a file and not a directory) it'll create a file named destination and overwrite that with all the files making you loose all your files (except the last one moved).
Explanation for the shift from + to \; in -exec taken directly from my man find page:
-exec command ;
All following arguments to find are taken to be arguments to the command until an argument consisting of ; is encountered. The string
{} is replaced by the current file name being processed everywhere it occurs in the arguments to the command, ... The specified command is run once for each matched file. ...
-exec command {} +
This variant of the -exec action runs the specified command on the selected files, but the command line is built by appending each selected file name at the end; The command line is built in much the same way that xargs builds its command lines. Only one instance of {} is allowed within the command, ...
Note that the syntax differs and that {} + must be at the end (where it's appending files) for the second version.

Find Files Containing Certain String and Copy To Directory Using Linux

I am trying to find files that contain a certain string in a current directory and make a copy of all of these files into a new directory.
My scrip that I'm trying to use
grep *Qtr_1_results*; cp /data/jobs/file/obj1
I am unable to copy and the output message is:
Usage: cp [-fhipHILPU][-d|-e] [-r|-R] [-E{force|ignore|warn}] [--] src target
or: cp [-fhipHILPU] [-d|-e] [-r|-R] [-E{force|ignore|warn}] [--] src1 ... srcN directory
Edit: After clearing things up (see comment)...
cp *Qtr_1_results* /data/jobs/file/obj1
What you're doing is just greping for nothing. With ; you end the command and cp prints the error message because you only provide the source, not the destination.
What you want to do is the following. First you want to grep for the filename, not the string (which you didn't provide).
grep -l the_string_you_are_looking_for *Qtr_1_results*
The -l option gives you the filename, instead of the line where the_string_you_are_looking_for is found. In this case grep will search in all files where the filename contains Qtr_1_results.
Then you want send the output of grep to a while loop to process it. You do this with a pipe (|). The semicolon ; just ends lines.
grep -l the_string_you_are_looking_for *Qtr_1_results* | while read -r filename; do cp $filename /path/to/your/destination/folder; done
In the while loop read -r will put the output of grep into the variable filename. When you assing a value to a variable you just write the name of the variable. When you want to have the value of the variable, you put a $ in front of it.
You can use multiple exec in find to do this task
For eg:
find . -type f -exec grep -lr "Qtr_1_results" {} \; -exec cp -r {} /data/jobs/file/obj1 \;
Details:
Find all files that contains the string. grep -l will list the files.
find . -type f -exec grep -lr "Qtr_1_results" {} \;
Result set from first part is a list of files. Copy each files from the result to destination.
-exec cp -r {} /data/jobs/file/obj1 \;

How to give o/p of one command to the argument of another command in shell script?

i have compiled Linux kernel now i want to copy all *.ko files in one separate folder
so
find ./kernel -name "*.ko"
It will give me the list of all .ko files.
Now i want to give this list as argument to cp command.
like
cp -rpf $filer_ko temp/
so how to do this in shell script and on terminal?
This will find all the files that end in .ko and will pipe that list in a loop that iterates over each file (even if they have spaces -- that's probably not the case here though) and will copy each file in the temp directory.
find ./kernel -name "*.ko" | while read file; do cp $file temp/ ; done
As per what i think the below code should satisfy your need. I only gave a dry run to it.
Store the result in a variable and then looping to extract data one by one
#! /bin/bash
$op = find ./kernel -name *.ko"
for zf in $op
do
cp -rpf $zf
tail $zf
done
xargs is normally used for this. But not here.
See, find has this ability builtin
Try this:
find ./kernel -name "*.ko" -exec cp -rpf {} temp/ \;
another approach: use xargs
find ./kernel -name "*.ko" -print0 | xargs -0 cp -rpf -t temp/

Backup files with dir structure bash script

I'm making a bash script that should backup all files and dir structure to another dir.
I made the following code to do that:
find . -type f -exec cp {} $HOME/$bdir \; -o -type d -exec mkdir -p {} $HOME/$bdir \; ;
The problem is, is that this only copies the files and not the dir structure.
NOTE: I may not use cp -r, cp -R or something like it because this code is part of an assignment.
I hope somebody can put me in the right direction. ;)
Joeri
EDIT:
I changed it to:
find . -type d -exec mkdir -p $HOME/$bdir/{} \; ;
find . -type f -exec cp {} $HOME/$bdir/{} \; ;
And it works! Ty guys ;)
This sounds like a job for rsync.
You mention that this is an assignment. What are your restrictions? Are you limited to only using find? Does it have to be a single command?
One way to do this is to do it in two find calls. The first call only looks for directories. When a directory is found, mkdir the corresponding directory in the destination hierarchy. The second find call would look for files, and would use a cp command like you currently have.
You can also take each filename, transform the path manually, and use that with the cp command. Here's an example of how to generate the destination filename:
> find . -type f | sed -e "s|^\./|/new/dir/|"
/new/dir/file1.txt
/new/dir/file2.txt
/new/dir/dir1/file1_1.txt
/new/dir/dir1/file1_2.txt
For your purposes, you could write a short bash script that take the source file as input, uses sed to generate the destination filename, and then passes those two paths to cp. The dirname command will return the directory portion of a filename, so mkdir -p $(dirname $destination_path) will ensure that the destination directory exists before you call cp. Armed with a script like that, you can simply have find execute the script for every file it finds.
cd olddir; tar c . | (cd newdir; tar xp)
Can you do your find with "-type d" and exec a "mkdir -p" first, followed by your find that copies the files rather than having it all in one command? It should probably also be mkdir -p $HOME/$bdir/{}.

Using a while/for loop with the 'find' command to copy files and directories

I have the follwoing problem: I want to make a script that backups a certain directory completely to another directory. I may not use cp -r or any other recursive command. So I was thinking of using a while or for loop. The directory that needs to be back upped is given with a parameter. This is what I have so far:
OIFS="$IFS"
IFS=$'\n'
for file in `find $1`
do
cp $file $HOME/TestDirectory
done
IFS="$OIFS"
But when I execute it, this is what my terminal says: Script started, file is typescript
Try this:
find "$1" -type f -print0 | xargs -0 cp -t $HOME/TestDirectory
Don't run your script through script !
Add this (shebang) at top of file:
#!/bin/bash
find "$1" -type f -print0 | xargs -0 cp -t $HOME/TestDirectory
change permission of your script for adding executable flag:
chmod +x myScript
run your script localy whith arguments:
./myScript rootDirectoryWhereSearchForFiles

Resources