input file is output file error - bash

I'm trying to run the command
find . -name "*.csv" | xargs -I{} cat '{}' > Everything2.csv
and I get back:
cat: ./Everything2.csv: input file is output file
What does this mean?

As shown in that answer, you should run:
$ find . -name '*.csv' -exec cat {} + | tee Everything2.csv
since redirection operator (> or >>) has a higher precedence, therefore it creating/truncating the file, before the find command is invoked. So to avoid that you need to generate the list first, then pipe it into the file, but without using redirection operator, so tee in this cause works fine.
Alternatively use sponge instead of cat which soaks up standard input and write to a file:
find . -name "*.csv" | xargs -I{} sponge '{}' > Everything2.csv

Tell find to exclude the output file from its results to prevent this loop:
find . -name Everything2.csv -prune -o \
-name '*.csv' -exec cat {} + \
>Everything2.csv

Related

Copying the result of a find operation in shell

I want to find a file, and simultaneously copy it to another directory like this:
cp (find . -name myFile | tail -n 1) dir/to/copy/to
But this says unexpected token `find'
Is there a better way to do this?
You may use a pipeline:
find . -name 'myFile' -print0 | tail -z -n 1 | xargs -0 -I {} cp {} /dir/to/copy/to/
Using -print0 option to address filenames with whitespace, glob characters
find . -name 'myFile' -print0 | tail -n 1 | xargs -0 -I {} cp {} /dir/to/copy/to/
Two options are available-
Appended the missing $() - to evaluate command (not sure the purpose of tail command, only required for samefile in multiple directories)
cp $(find . -name myFile | tail -n 1) dir/to/copy/to
find . -name myFile -type f -exec cp {} dir/to/copy/to \;

renaming series of files using xargs

I would like to rename several files picked by find in some directory, then use xargs and mv to rename the files, with parameter expansion. However, it did not work...
example:
mkdir test
touch abc.txt
touch def.txt
find . -type f -print0 | \
xargs -I {} -n 1 -0 mv {} "${{}/.txt/.tx}"
Result:
bad substitution
[1] 134 broken pipe find . -type f -print0
Working Solution:
for i in ./*.txt ; do mv "$i" "${i/.txt/.tx}" ; done
Although I finally got a way to fix the problem, I still want to know why the first find + xargs way doesn't work, since I don't think the second way is very general for similar tasks.
Thanks!
Remember that shell variable substitution happens before your command runs. So when you run:
find . -type f -print0 | \
xargs -I {} -n 1 -0 mv {} "${{}/.txt/.tx}"
The shell tries to expan that ${...} construct before xargs even
runs...and since that contents of that expression aren't a valid shell variable reference, you get an error. A better solution would be to use the rename command:
find . -type f -print0 | \
xargs -I {} -0 rename .txt .tx {}
And since rename can operate on multiple files, you can simplify
that to:
find . -type f -print0 | \
xargs -0 rename .txt .tx

What is the correct Linux command of find, grep and sort?

I am writing a command using find, grep and sort to display a sorted list of all files that contain 'some-text'.
I was unable to figure out the command.
Here is my attempt:
$find . -type f |grep -l "some-text" | sort
but it didn't work.
You need to use something like XARGS so that the content of each file passed through the pipe | is made available for grep.
XARGS: converts input from standard input into arguments to a command
In my case, I have files1,2,3 and they contain the word test. This will do it.
za:tmp za$ find . -type f | xargs grep -l "test" | sort
./file1.txt
./file2.txt
./file3.txt
or
za:tmp za$ find . -type f | xargs grep -i "test" | sort
./file1.txt:some test string
./file2.txt:some test string
./file3.txt:some test string
You can use it in any unix:
find . -type f -exec sh -c 'grep "some text" {} /dev/null > /dev/null 2>&1' \; -a -print 2> /dev/null|sort
A more optimized solution that works only with GNU-grep:
find . -type f -exec grep -Hq "some-text" {} \; -a -print 2> /dev/null|sort

Format all XML files in a directory and save them in a subdirectory

I'm trying to write a script that will look through a directory, find all the XML files, run them through xmllint, and save the formatted results to a file of the same name in a subdirectory called formatted. Here's the script I have so far:
find . -maxdepth 1 -type f -iname "*.xml" | xargs -I '{}' xmllint --format '{}' > formatted/'{}'
This works, to an extent. The subdirectory ends up with one file, named "{}", which is just the results of the final file that was processed through xmllint. How can I get the files to write properly to the subdirectory?
The file named {} that you see should probably contain all of the formatted files together. The reason for this is that the redirection that you are using is not actually a part of the command that xargs sees. The redirection is interpreted by the shell, so what it does is run
find . -maxdepth 1 -type f -iname "*.xml" | xargs -I '{}' xmllint --format '{}'
and save the output to the file named formatted/{}.
Try using the --output option of xmllint instead of the redirection:
... | xargs -I '{}' xmllint --format '{}' --output formatted/'{}'
You can also avoid calling xargs by using the -exec option of find:
find . -maxdepth 1 -type f -iname "*.xml" -exec xmllint --format '{}' --output formatted/'{}' \;

Better way to limit the unix command find by filename

I'm getting results using find with filenames that have '~' and .swp, etc. So I did the following, but is there a better way to do this? The '.*.js' -iname '*.js' part feels "redundant".
$ find ./ '.*.js' -iname '*.js' -print0 | xargs -0 grep -n ".*loginError.*"
find: `.*.js': No such file or directory
./js/signin.js:252: foo.loginError();
./js/signin.js:339:foo.loginError = function() {
./js/signin.js:340: foo.log("ui.loginError");
Try using
find . -name \*.js -print0 | xargs -0 grep -n ".*loginError.*"
That will find only files with 'js' extension and not ending in ~ or .swp
EDIT: Added '0' -print0 (edit requires 6 characters so I'm adding this; ergh!)
To do it all in one command without the xargs you could do it like this
find . -name "*.js" -exec grep -n ".*loginError.*" /dev/null {} \;
the /dev/null piece is to make grep think it's searching multiple files and then it'll output the filename correctly, otherwise it'd just print out the line number without telling you which file it's in

Resources