In bash, I think I know how to iterate over an array of strings containing spaces:
~ $ arr=( "/home/user/Images/three parts dirname/ccc.png" "/home/user/Images/three parts dirname/bbb.png" "/home/user/Images/three parts dirname/aaa.png" )
~ $ for i in "${arr[#]}"; do echo "$i"; done
/home/user/Images/three parts dirname/ccc.png
/home/user/Images/three parts dirname/bbb.png
/home/user/Images/three parts dirname/aaa.png
I want to do something similar in a script that uses find. So I'm doing...
~ $ d="/home/user/Images/three parts name"
~ $ arr=( $(find -L "$d" -maxdepth 1 -iname '*.jp*g' -o -iname '*.png' -printf '"%p" ') )
...because the command find -L "$d" -maxdepth 1 -iname '*.jp*g' -o -iname '*.png' -printf '"%p" '
gives exacltly:
"/home/user/Images/three part name/ccc.png" "/home/user/Images/three part name/bbb.png" "/home/user/Images/three part name/aaa.png"
The problem is that in this case I have the result below:
~ $ for i in "${arr[#]}"; do echo "$i"; done
"/home/user/Images/three
parts
name/ccc.png"
"/home/user/Images/three
parts
name/bbb.png"
"/home/user/Images/three
parts
name/aaa.png"
So I cannot iterate over those files successfully. I know I can avoid spaces in dirs and files names (and I barely have any) but I wanted the script to work anyway, just in case.
What I mean is: why If I use find to define the array, the 1st item, i.e., is
"/home/user/Images/three
and not
/home/user/Images/three parts dirname/ccc.png ?
It seems that with find the spaces in the dir names are "hard coded" and/or the double quotes are part of the strings (separated by spaces) themselves.
EDIT here for clarity:
I accepted the answer using find. It has to be corrected adding escaped brackets though, according to stackoverflow.com/a/6957310/1865860. I also wanted to further process the find results, so I had to rely on stackoverflow.com/a/11789688/1865860.
The final output looks like:
readarray -d $'\0' TOT_IMAGES < <(find -L "$i" -maxdepth 1 \( -iname '*.jp*g' -o -iname '*.png' -o -iname '*.gif' -o -iname '*.tif*' \) -print0);
IFS=$'\n';
SORTED_IMAGES=($(sort <<<"${TOT_IMAGES[*]}"));
TOP4_IMAGES=($(head -n4 <<<"${SORTED_IMAGES[*]}"));
unset IFS
You could do this with pure bash, without using the find:
shopt -s nullglob nocaseglob
arr=("$d"/{*.png,*.jp*g})
You can use find with print0 option to have ASCII NUL character separator (instead of NL) and then use readarray
readarray -d $'\0' arr < <(find -L "$d" -maxdepth 1 -iname '*.jp*g' -o -iname '*.png' -print0)
so you don't have to worry about the spaces.
I have a cronjob that runs every 24 hours to tell me if files on my server have changed. The script is as such:
find /home/bsc1933 -type f -ctime -1 -exec ls -ls {} \; | mail -E -s “File Changes, Past 24 Hours” myemail
I would like to modify it to exclude a specific folder, in this case my cache folder: /home/bsc1933/public_html/cache
I found the original script with Google-fu and just edited the email to match mine, so my knowledge of actually editing the script itself is non-existent. Could someone help me?
Simplest answer is as follows, by adding "grep" to filter out path you wish to exclude, between "find" and "mail" command.
`find /home/bsc1933 -type f -ctime -1 -exec ls -ls {} \; | grep -v '/home/bsc1933/public_html/cache' |` ... your code to send email ...
Let me know if you face any other difficulties.
Try using grep or find to ignore entries with a certain pattern.
With grep:
find /home/bsc1933 -type f -ctime -1 -exec ls -ls {} \; |grep -Ev "/home/bsc1933/public_html/cache" | mail -E -s “File Changes, Past 24 Hours” myemail
Or telling find to ignore it:
find /home/bsc1933 ! -path '*/.cache/*' -type f -ctime -1 -exec ls -ls {} \; | mail -E -s “File Changes, Past 24 Hours” myemail
Tried using:
find /home/ -user student -exec rm -fr {}
or
find /home/ -group student -exec rm -fr {} \
but had error message: find: missing argument to '-exec'.
Welcome to stackoverflow!
The easiest way (even mentioned in manual page for [find(1)][1]) is:
find /tmp -name core -type f -print | xargs /bin/rm -f
adapted to your case:
find /home/ -user student -print | xargs /bin/rm -rf
But certainly there are several variants how to accomplish this task. You can simply start from different example with -exec:
find . -type f -exec file '{}' \;
adapted to your use case:
find /home/ -group student -exec rm -rf '{}' \;
I have multiple(more than 100) .c files and I want to change a particular text from all the file in which that text exists. I am using ubuntu!
How can I do it?(I will prefer command line rather than installing any application)
Thanks a lot!
OLD=searchtext
NEW=replacedtext
YOURFILE=/path/to/your/file
TMPFILE=`mktemp`
sed "s/$OLD/$NEW/g" $YOURFILE > $TMPFILE && mv $TMPFILE $YOURFILE
rm -rf $TMPFILE
you can also use find to find your files:
find /path/to/parent/dir -name "*.c" -exec sed 's/$OLD/$NEW/g' {} > $TMPFILE && mv $TMPFILE {} \;
find /path/to/parent/dir -name "*.c" finds all files with name *.c under /path/to/parent/dir. -exec command {} \; executes the command in the found file. {} stands for the found file.
You should check out sed, which lets your replace some text with other text (among other things)
example
sed s/day/night/ oldfile newfile
will change all occurences of "day" with "night" in the oldfile, and store the new, changed version in the newfile
to run on many files, there are a few things you could do:
use foreach in your favorite shell
use find like this
find . -name "namepattern" -exec sed -i "sed-expr" "{}" \;
use file patterns like this: sed -i "sed-expr" *pattern?.cpp
where *pattern?.cpp is just a name pattern for all files that starts with some string, then has "pattern" in them, and has any letter and a ".cpp" suffix
How do I find out the files in the current directory which do not contain the word foo (using grep)?
If your grep has the -L (or --files-without-match) option:
$ grep -L "foo" *
You can do it with grep alone (without find).
grep -riL "foo" .
This is the explanation of the parameters used on grep
-L, --files-without-match
each file processed.
-R, -r, --recursive
Recursively search subdirectories listed.
-i, --ignore-case
Perform case insensitive matching.
If you use l (lowercased) you will get the opposite (files with matches)
-l, --files-with-matches
Only the names of files containing selected lines are written
Take a look at ack. It does the .svn exclusion for you automatically, gives you Perl regular expressions, and is a simple download of a single Perl program.
The equivalent of what you're looking for should be, in ack:
ack -L foo
The following command gives me all the files that do not contain the pattern foo:
find . -not -ipath '.*svn*' -exec grep -H -E -o -c "foo" {} \; | grep 0
The following command excludes the need for the find to filter out the svn folders by using a second grep.
grep -rL "foo" ./* | grep -v "\.svn"
If you are using git, this searches all of the tracked files:
git grep -L "foo"
and you can search in a subset of tracked files if you have ** subdirectory globbing turned on (shopt -s globstar in .bashrc, see this):
git grep -L "foo" -- **/*.cpp
You will actually need:
find . -not -ipath '.*svn*' -exec grep -H -E -o -c "foo" {} \; | grep :0\$
I had good luck with
grep -H -E -o -c "foo" */*/*.ext | grep ext:0
My attempts with grep -v just gave me all the lines without "foo".
Problem
I need to refactor a large project which uses .phtml files to write out HTML using inline PHP code. I want to use Mustache templates instead. I want to find any .phtml giles which do not contain the string new Mustache as these still need to be rewritten.
Solution
find . -iname '*.phtml' -exec grep -H -E -o -c 'new Mustache' {} \; | grep :0$ | sed 's/..$//'
Explanation
Before the pipes:
Find
find . Find files recursively, starting in this directory
-iname '*.phtml' Filename must contain .phtml (the i makes it case-insensitive)
-exec 'grep -H -E -o -c 'new Mustache' {}' Run the grep command on each of the matched paths
Grep
-H Always print filename headers with output lines.
-E Interpret pattern as an extended regular expression (i.e. force grep
to behave as egrep).
-o Prints only the matching part of the lines.
-c Only a count of selected lines is written to standard output.
This will give me a list of all file paths ending in .phtml, with a count of the number of times the string new Mustache occurs in each of them.
$> find . -iname '*.phtml$' -exec 'grep -H -E -o -c 'new Mustache' {}'\;
./app/MyApp/Customer/View/Account/quickcodemanagestore.phtml:0
./app/MyApp/Customer/View/Account/studio.phtml:0
./app/MyApp/Customer/View/Account/orders.phtml:1
./app/MyApp/Customer/View/Account/banking.phtml:1
./app/MyApp/Customer/View/Account/applycomplete.phtml:1
./app/MyApp/Customer/View/Account/catalogue.phtml:1
./app/MyApp/Customer/View/Account/classadd.phtml:0
./app/MyApp/Customer/View/Account/orders-trade.phtml:0
The first pipe grep :0$ filters this list to only include lines ending in :0:
$> find . -iname '*.phtml' -exec grep -H -E -o -c 'new Mustache' {} \; | grep :0$
./app/MyApp/Customer/View/Account/quickcodemanagestore.phtml:0
./app/MyApp/Customer/View/Account/studio.phtml:0
./app/MyApp/Customer/View/Account/classadd.phtml:0
./app/MyApp/Customer/View/Account/orders-trade.phtml:0
The second pipe sed 's/..$//' strips off the final two characters of each line, leaving just the file paths.
$> find . -iname '*.phtml' -exec grep -H -E -o -c 'new Mustache' {} \; | grep :0$ | sed 's/..$//'
./app/MyApp/Customer/View/Account/quickcodemanagestore.phtml
./app/MyApp/Customer/View/Account/studio.phtml
./app/MyApp/Customer/View/Account/classadd.phtml
./app/MyApp/Customer/View/Account/orders-trade.phtml
When you use find, you have two basic options: filter results out after find has completed searching or use some built in option that will prevent find from considering those files and dirs matching some given pattern.
If you use the former approach on a high number of files and dirs. You will be using a lot of CPU and RAM just to pass the result on to a second process which will in turn filter out results by using a lot of resources as well.
If you use the -not keyword which is a find argument, you will be preventing any path matching the string on the -name or -regex argument behind from being considered, which will be much more efficient.
find . -not -regex ".*/foo/.*" -regex ".*"
Then, any path that is not filtered out by -not will be captured by the subsequent -regex arguments.
For completeness the ripgrep version:
rg --files-without-match "pattern"
You can combine with file type and search path, e.g.
rg --files-without-match -t ruby "frozen_string_literal: true" app/
another alternative when grep doesn't have the -L option (IBM AIX for example), with nothing but grep and the shell :
for file in * ; do grep -q 'my_pattern' $file || echo $file ; done
My grep does not have any -L option. I do find workaround to achieve this.
The ideas are :
to dump all the file name containing the deserved string to a txt1.txt.
dump all the file name in the directory to a txt2.txt.
make the difference between the 2 dump file with diff command.
grep 'foo' *.log | cut -c1-14 | uniq > txt1.txt
grep * *.log | cut -c1-14 | uniq > txt2.txt
diff txt1.txt txt2.txt | grep ">"
find *20161109* -mtime -2|grep -vwE "(TRIGGER)"
You can specify the filter under "find" and the exclusion string under "grep -vwE". Use mtime under find if you need to filter on modified time too.
Open bug report
As commented by #tukan, there is an open bug report for Ag regarding the -L/--files-without-matches flag:
ggreer/the_silver_searcher: #238 - --files-without-matches does not work properly
As there is little progress to the bug report, the -L option mentioned below should not be relied on, not as long as the bug has not been resolved. Use different approaches presented in this thread instead. Citing a comment for the bug report [emphasis mine]:
Any updates on this? -L completely ignores matches on the first line of the file. Seems like if this isn't going to be fixed soon, the flag should be removed entirely, as it effectively does not work as advertised at all.
The Silver Searcher - Ag (intended function - see bug report)
As a powerful alternative to grep, you could use the The Silver Searcher - Ag:
A code searching tool similar to ack, with a focus on speed.
Looking at man ag, we find the -L or --files-without-matches option:
...
OPTIONS
...
-L --files-without-matches
Only print the names of files that don´t contain matches.
I.e., to recursively search for files that do not match foo, from current directory:
ag -L foo
To only search current directory for files that do not match foo, simply specify --depth=0 for the recursion:
ag -L foo --depth 0
This may help others. I have mix of files Go and with test files. But I only need .go files. So I used
ls *.go | grep -v "_test.go"
-v, --invert-match select non-matching lines see https://stackoverflow.com/a/3548465
Also one can use this with vscode to open all the files from terminal
code $(ls *.go | grep -v "_test.go")
grep -irnw "filepath" -ve "pattern"
or
grep -ve "pattern" < file
above command will give us the result as -v finds the inverse of the pattern being searched
The following command could help you to filter the lines which include the substring "foo".
cat file | grep -v "foo"