How to use 'cat' command with -exec in bash? [duplicate] - linux

This question already has answers here:
How to move or copy files listed by 'find' command in unix?
(5 answers)
Redirecting stdout with find -exec and without creating new shell
(3 answers)
Closed 4 years ago.
I search for some files and I want to replace old content with new content from other files, so I have
#!/bin/bash
find /home/old -path "*$1" -exec cat /home/new/$1 > {} \;
When I execut:
> sh find.sh 'blam.php'
Problem is the script is creating a file called {} in the same dir with find.sh

The problem you get is that > is part of shell syntax, and should be literal for find command and syntactic in exec command.
what you are trying to do may be done in two steps
generating script
executing the script
.
find /home/old -path "*$1" -printf 'cat /home/new/'"$1"' > %p\n' > script.sh
sh script.sh
this way allows to check the commands, or using pipe
find /home/old -path "*$1" -printf 'cat /home/new/'"$1"' > %p\n' | sh
another way less efficient because spwans a shell for each file
find /home/old -path "*$1" -exec sh -c 'cat "/home/new/$1" > {}' _ "$1" \;

The problem here is that > {} is not part of the find command. The script is first interpreted by the shell, and the meaning of > is handled by the shell before passing the stuff around it as arguments to execute:
$ set -- blam.php
$ set -x
$ find /home/old -path "*$1" -exec cat "/home/new/$1" > {} \;
+ find /home/old -path '*blam.php' -exec cat /home/new/blam.php ';'
If you want a copy of your file somewhere else, use cp.

Related

How to use grep to reverse search files in a folder

I'm trying to create a script which will find missing topics from multiple log files. These logfiles are filled top down, so the newest logs are at the bottom of the file. I would like to grep only the last line from this file which includes UNKNOWN_TOPIC_OR_PARTITION. This should be done in multiple files with completely different names. Is grep the best solution or is there another solution that suits my needs. I already tried adding tail, but that doesn't seem to work.
missingTopics=$(grep -Ri -m1 --exclude=*.{1,2,3,4,5} UNKNOWN_TOPIC_OR_PARTITION /app/tibco/log/tra/domain/)
You could try a combination of find, tac and grep:
find /app/tibco/log/tra/domain -type f ! -name '*.[1-5]' -exec sh -c \
'tac "$1" | grep -im1 UNKNOWN_TOPIC_OR_PARTITION' "sh" '{}' \;
tac prints files in reverse, the -exec sh -c SCRIPT "sh" '{}' \; action of find executes the shell SCRIPT each time a file matching the previous tests is found. The SCRIPT is executed with "sh" as parameter $0 and the path of the found file as parameter $1.
If performance is an issue you can probably improve it with:
find . -type f ! -name '*.[1-5]' -exec sh -c 'for f in "$#"; do \
tac "$f" | grep -im1 UNKNOWN_TOPIC_OR_PARTITION; done' "sh" '{}' +
which will spawn less shells. If security is also an issue you can also replace -exec by -execdir (even if with this SCRIPT I do not immediately see any exploit).

Using find command to grab the file extensions in directory in shell script [duplicate]

This question already has answers here:
How can I find all of the distinct file extensions in a folder hierarchy?
(17 answers)
Closed 1 year ago.
So far I have tried
extension=$(find /home/path-to-dir -type f -name '*.*' | sed 's/^.*\.//' | sort -u)
It is giving me extension right after . but it is failing for text.data.json ( I only need json)
similar example stack/~.hello.py ( I only need py)
also I m trying to find the creation date of a file ? is it possible ?
As an alternative to the sed command, we can use parameter expansion to get the string after the last .;
find /home/path-to-dir -type f -name '*.*' -exec bash -c 'echo ${0##*.}' {} \;
Small example:
--> ls
ahahha~bla.py bla.bla.json ok.py test.json text.data.json
-->
-->
--> find . -type f -name '*.*' -exec bash -c 'echo ${0##*.}' {} \; | sort -u
json
py
Extract filename and extension in Bash
Using this i solved it
find . -type f -name "[^.]*.*" -exec bash -c 'printf "%s\000" "${###*.}"' argv0 '{}' + | sort -uz | tr '\0' '\n'

Save output command in a variable and write for loop

I want to write a shell script. I list my jpg files inside nested subdirectories with the following command line:
find . -type f -name "*.jpg"
How can I save the output of this command inside a variable and write a for loop for that? (I want to do some processing steps for each jpg file)
You don't want to store output containing multiple files into a variable/array and then post-process it later. You can just do those actions on the files on-the-run.
Assuming you have bash shell available, you could write a small script as
#!/usr/bin/env bash
# ^^^^ bash shell needed over any POSIX shell because
# of the need to use process-substitution <()
while IFS= read -r -d '' image; do
printf '%s\n' "$image"
# Your other actions can be done here
done < <(find . -type f -name "*.jpg" -print0)
The -print0 option writes filenames with a null byte terminator, which is then subsequently read using the read command. This will ensure the file names containing special characters are handled without choking on them.
Better than storing in a variable, use this :
find . -type f -name "*.jpg" -exec command {} \;
Even, if you want, command can be a full bloated shell script.
A demo is better than an explanation, no ? Copy paste the whole lines in a terminal :
cat<<'EOF' >/tmp/test
#!/bin/bash
echo "I play with $1 and I can replay with $1, even 3 times: $1"
EOF
chmod +x /tmp/test
find . -type f -name "*.jpg" -exec /tmp/test {} \;
Edit: new demo (from new questions from comments)
find . -type f -name "*.jpg" | head -n 10 | xargs -n1 command
(this another solution doesn't take care of filenames with newlines or spaces)
This one take care :
#!/bin/bash
shopt -s globstar
count=0
for file in **/*.jpg; do
if ((++count < 10)); then
echo "process file $file number $count"
else
break
fi
done

How to chop off part of a file extension using find -exec

I want to use "find" to rename a bunch of files, with the rename simply being the removal of part of the extension.
EXAMPLE:
abc.ext.DELAYED --> abc.ext
I've tried the following, but they simply aren't working:
find . -name *.DELAYED -execdir mv {} $(echo {} | sed 's:\.DELAYED::') \;
find . -name *.DELAYED -execdir mv {} $(echo {} | cut -f 1 -d".") \;
There are two problems with your commands.
The first problem is the * in the command. You need to enclose it in a string since otherwise bash would expand it as a glob expression - * expands to all files in the current folder.
The command should look like this:
find . -name '*.DELAYED' ...
The second problem is that command substitutions happen before the command gets executed meaning
$(echo {})
would expaneded to the literal {} will would lead to a command like
mv file1 file1
You can execute the command in a shell instead:
... -execdir bash -c 'mv {} $(echo {} | cut -f2 -d.)' \;
You have specifically tagged this question with "linux", so I assume that your distribution has the rename tool installed, which is bundled in util-linux package.
This avoids command substitution issues and chaining multiple programs:
find . -name '*.DELAYED' -execdir rename .DELAYED '' {} \;

Run expand on find results

I'm trying to run the expand shell command on all files found by a find command. I've tried -exec and xargs but both failed. Can anyone explain me why? I'm on a mac for the record.
find . -name "*.php" -exec expand -t 4 {} > {} \;
This just creates a file {} with all the output instead of overwriting each individual found file itself.
find . -name "*.php" -print0 | xargs -0 -I expand -t 4 {} > {}
And this just outputs
4 {}
xargs: 4: No such file or directory
Your command does not work for two reasons.
The output redirection is done by the shell and not by find. That means that the shell will redirect finds output into the file {}.
The redirection would occur immediately. That means that the file will be written even before it is read by the expand command. So it's not possible to redirect a command's output into the input file.
Unfortunately expand doesn't allow to write it's output into a file. So you have to use output redirection. If you use bash you could define a function that executes expand, redirects the output into a temporary file and move the temporary file back over the original file. The problem is that find will run a new shell to execute the expand command.
But there is a solution:
expand_func () {
expand -t 4 "$1" > "$1.tmp"
mv "$1.tmp" "$1"
}
export -f expand_func
find . -name \*.php -exec bash -c 'expand_func {}' \;
You are exporting the function expand_func to sub shells using export -f. And you don't execute expand itself using find -exec but you execute a new bash that executes the exported expand_func.
'expand' isn't really worth the trouble.
You can just use sed instead:
find . -name "*.php" | xargs sed -i -e 's/\t/ /g'

Resources