How to find and move all files matching given Beginning Of File (BOF) string? - bash

How to move all files which content begins with foo to another folder with command line ?
I tried this to echo filenames when matching:
for f in *.txt; do if [ $(head -c5 $f) = "foo" ]; then echo $f; fi; done;
but I'm often getting this error:
-bash: [: too much arguments

Use a Perl one-liner in combination with find and xargs, like so:
echo foo > 1.txt
echo "foo\nbar" > 2.txt
echo "bar\nfoo" > 3.txt
mkdir foodir
find . -maxdepth 1 -name '[123].txt' -exec perl -lne 'print $ARGV if /^foo/; last;' {} \; | xargs -I{} mv {} foodir
find foodir -type f
# foodir/2.txt
# foodir/1.txt

Find with awk
find . -type f -exec awk -F \/ "NR < 6 && /foo/ { fnd=1 } END { if (fnd==1) { split(FILENAME,arr,\"/\");print \"mv -f \"FILENAME\" newdir/\"arr[length(arr)] } }" '{}' \;
Use awk to process the first 5 lines (NR < 6). Search for foo and if it exists, set a fnd variable to 1. At the end, if fnd is 1, print the mv command, using split to get the filename without the directories. Check that everything looks as expected and then run with:
find . -type f -exec awk -F \/ "NR < 6 && /foo/ { fnd=1 } END { if (fnd==1) { split(FILENAME,arr,\"/\");print \"mv -f \"FILENAME\" newdir/\"arr[length(arr)] } }" '{}' \; | bash

Related

Count the number of files in a directory containing two specific string in bash

I have few files in a directory containing below pattern:
Simulator tool completed simulation at 20:07:18 on 09/28/18.
The situation of the simulation: STATUS PASSED
Now I want to count the number of files which contains both of strings completed simulation & STATUS PASSED anywhere in the file.
This command is working to search for one string STATUS PASSED and count file numbers:
find /directory_path/*.txt -type f -exec grep -l "STATUS PASSED" {} + | wc -l
Sed is also giving 0 as a result:
find /directory_path/*.txt -type f -exec sed -e '/STATUS PASSED/!d' -e '/completed simulation/!d' {} + | wc -l
Any help/suggestion will be much appriciated!
find . -type f -exec \
awk '/completed simulation/{x=1} /STATUS PASSED/{y=1} END{if (x&&y) print FILENAME}' {} \; |
wc -l
I'm printing the matching file names in case that's useful in some other context but piping that to wc will fail if the file names contain newlines - if that's the case just print 1 or anything else from awk.
Since find /directory_path/*.txt -type f is the same as just ls /directory_path/*.txt if all of the ".txt"s are files, though, it sounds like all you actually need is (using GNU awk for nextfile):
awk '
FNR==1 { x=y=0 }
/completed simulation/ { x=1 }
/STATUS PASSED/ { y=1 }
x && y { cnt++; nextfile }
END { print cnt+0 }
' /directory_path/*.txt
or with any awk:
awk '
FNR==1 { x=y=f=0 }
/completed simulation/ { x=1 }
/STATUS PASSED/ { y=1 }
x && y && !f { cnt++; f=1 }
END { print cnt+0 }
' /directory_path/*.txt
Those will work no matter what characters are in your file names.
Using grep and standard utils:
{ grep -Hm1 'completed simulation' /directory_path/*.txt;
grep -Hm1 'STATUS PASSED' /directory_path/*.txt ; } |
sort | uniq -d | wc -l
grep -m1 stops when it finds the first match. This saves time if it's a big file. If the list of matches is large, sort -t: -k1 would be better than sort.
The command find /directory_path/*.txt just lists all txt files in /directory_path/ not including subdirectories of /directory_path
find . -name \*.txt -print0 |
while read -d $'\0' file; do
grep -Fq 'completed simulation' "$file" &&
grep -Fq 'STATUS PASSED' "$_" &&
echo "$_"
done |
wc -l
If you ensure no special characters in the filenames
find . -name \*.txt |
while read file; do
grep -Fq 'completed simulation' "$file" &&
grep -Fq 'STATUS PASSED' "$file" &&
echo "$file"
done |
wc -l
I don't have AIX to test it, but it should be POSIX compliant.

FIND folders and MV (rename) them using FIND, GREP, XARGS, and AWK?

I'm trying to move my LOG folders. Here is what I have so far.
cd archive
find .. -type d -name 'LOGS' | xargs -I '{}' mv {} `echo {} | awk -F/ 'NF > 1 { print $(NF - 1)"-LOGS"; }'`
Unfortunately --> echo {} | awk -F/ 'NF > 1 { print $(NF - 1)"-LOGS"; }' <-- evaluates immediately. So doesn't give me the file name that I would prefer.
mv ../app1/LOGS app1-LOGS
mv ../app2/LOGS app2-LOGS
Is there a way to do this in a single line?
Using xargs:
find .. -type d -name 'LOGS' |
xargs -I {} bash -c 'd="${1%/*}"; mv "$1" "${d##*/}-LOGS"' - {}
Or else you can do that like this using process substitution:
cd archive
while IFS= read -rd '' dir; do
d="${dir%/*}"
d="${d##*/}"
mv "$dir" "$d-LOGS"
done < <(find .. -type d -name 'LOGS' -print0)

Finding a particular string at a specific line number, anywhere in a directory

I am trying to find instances of the word package in line 171 of any file in a certain directory. What is the best way to do this?
You can do, from the directory you want to check, recursively:
find . -type f -exec bash -c '[[ $(sed -n '171p' "$1") =~ package ]] && echo "$1"' _ {} +
this will show you the filenames that contain package in their 171-th line.
Non-recursively:
find . -maxdepth 1 -type f -exec bash -c '[[ $(sed -n '171p' "$1") =~ package ]] && echo "$1"' _ {} +
Example:
I am looking for bar:
$ cat foo
foo
bar
$ find . -type f -exec bash -c '[[ $(sed -n '2p' "$1") =~ bar ]] && echo "$1"' _ {} +
./foo
#This will search second line of all the files and grep over them.
#Pass search pattern as argument.
pattern=$1
for file in *
do
cat $file |sed -n '2p' |grep $pattern
done

Bash function find and rm

I am trying to do a recursive grep and deleting files with less than a specified entry.
To be more clear, I have a directory of 400000 text files and in each file i have 10 items each starting with the >. Now the problem is that some of the files out of the 4000000 files have only 6-7 or 8-9 items starting with >.
So I wish to delete the files which have fewer than 10 items. I am using the recursive function, however i am not able to figure out how to add rm in the recursive way. What I have till now is:
find . -name "*.[txt]" -exec grep ">" -c {} \;
You can use -exec like this:
find . -name "*.txt" -exec bash -c '(( $(grep ">" -c "$1") <= 10 )) && rm "$1"' - '{}' \;
To avoid creating shell per file you can use:
while read -r f; do
(( $(grep ">" -c "$f") <= 10 )) && rm "$f"
done < <(find . -name "*.txt")
I would break it up into smaller steps:
find . -type f -exec grep -c '>' {} + |
awk -F: '$2 != 10 {print $1}' |
xargs echo rm
remove the "echo" if you're satisfied it's working
The awk step is fragile if you have any filenames containing ":"

unix match multiple patterns return success

Is there an easy way to search a file for multiple patterns and return success only if both patterns are found.
For example if I had a file:
itemA
itemB
itemC
itemD
I want to print the name of all txt files that have both "itemA" and "itemD"
something like:
find . -name "*.txt" | xargs -n 1 -I {} sh -c "grep 'itemA AND itemB' && echo {}"
awk '/ItemA/{f1=1} /ItemB/{f2=1} END{ exit (f1 && f2 ? 0 : 1) }' file
find . -name "*.txt" -exec grep -l 'itemA' {} + | xargs grep -l 'itemB'
Add -Z to grep and -0 to xargs if you want to be extra careful with special characters.
Translating your pseudo-code into real:
find . -name "*.txt" | xargs -n 1 -I {} sh -c "grep -q itemA {} && grep -q itemD {} && echo {}"
You could shorten this somewhat by making the second grep print the filename:
find . -name "*.txt" | xargs -n 1 -I {} sh -c "grep -q itemA {} && grep -l itemD {}"

Resources