bash scripting: looping and file manipulation [duplicate] - linux

I have a list of images, collected using the following line:
# find . -mindepth 1 -type f -name "*.JPG" | grep "MG_[0-9][0-9][0-9][0-9].JPG"
output:
./DCIM/103canon/IMG_0039.JPG
./DCIM/103canon/IMG_0097.JPG
./DCIM/103canon/IMG_1600.JPG
./DCIM/103canon/IMG_2317.JPG
./DCIM/IMG_0042.JPG
./DCIM/IMG_1152.JPG
./DCIM/IMG_1810.JPG
./DCIM/IMG_2564.JPG
./images/IMG_0058.JPG
./images/IMG_0079.JPG
./images/IMG_1233.JPG
./images/IMG_1959.JPG
./images/IMG_2012/favs/IMG_0039.JPG
./images/IMG_2012/favs/IMG_1060.JPG
./images/IMG_2012/favs/IMG_1729.JPG
./images/IMG_2012/favs/IMG_2013.JPG
./images/IMG_2012/favs/IMG_2317.JPG
./images/IMG_2012/IMG_0079.JPG
./images/IMG_2012/IMG_1403.JPG
./images/IMG_2012/IMG_2102.JPG
./images/IMG_2013/IMG_0060.JPG
./images/IMG_2013/IMG_1311.JPG
./images/IMG_2013/IMG_1729.JPG
./images/IMG_2013/IMG_2013.JPG
./IMG_0085.JPG
./IMG_1597.JPG
./IMG_2288.JPG
however I only want the very last portion, the IMG_\d\d\d\d.JPG. I have tried hundreds of regular expressions and this is the one that gives me the best result. Is there a way to only print out the filename without the directory tree before it or is is solely down to the regex?
Thanks

It should be
find . -mindepth 1 -type f -name "*MG_[0-9][0-9][0-9][0-9].JPG" -printf "%f\n"

If the -printf option is not available with your implementation of find (as in current versions of Mac OS X),
then you can use -execdir echo {} \; instead (if that's available):
find . -mindepth 1 -type f -name "*MG_[0-9][0-9][0-9][0-9].JPG" -execdir echo {} \;

Related

errors popped out when using find and -exec for bash command

Below is the list of directories.
I want to remove all directories under those named started with "dummy_" and "log", which were created earlier than a year ago.
In other words, I want to delete those directories named by date.
-log
table_a
log1
2022-01-01
hi.py
2022-01-02
dummy_a
2021-10-10
hello.txt
2022-01-01
2022-02-02
dummy_b
2020-11-11
hey.docx
2021-03-28
table_b
log1
2022-01-11
log2
logger
table_c
dummy3
I've tried the following bash command to echo those directories but those errors popped out.
(I don't wanna remove all the directories so I used "-exec echo {} ;" to test instead)
What I expected it to show was
log/table_a/log1/2022-01-01
log/table_a/log1/2022-01-02
log/table_a/dummy_a/2021-10-10
log/table_a/dummy_a/2022-01-01
log/table_a/dummy_a/2022-02-02
log/table_a/dummy_b/2020-11-11
log/table_a/dummy_a/2022-02-02
log/table_a/dummy_b/2021-03-28
log/table_b/log1/2022-01-11
So i used the following command but there were some problems in different scenario
1.
find log/*/ \( -name log* -o -name dummy_* \) /* -type d -mtime +365 -exec echo {} \;
Result:
find: /Applications: unknown primary or operator
Then I tried another two methods
find log/*/ \( -name log* -o -name dummy_* \) -type d -exec find {} -type d -mtime +365 -exec echo {} \;
find log/*/ \( -name log* -o -name dummy_* \) -type d -exec find {} -type d -mtime +365 -exec echo {} \;\;
Result:
find: -exec: no terminating ";" or "+"
find: -exec: no terminating ";" or "+"
find: -exec: no terminating ";" or "+"
find: -exec: no terminating ";" or "+"
find: -exec: no terminating ";" or "+"
I feel like the second command is more close to what i expected but i don't know what's wrong with it.
Does anyone know how to solve this?
Appreciate it!
I think what you can do is use regex with find to search all the directories named by date older than one year, pipe that data to grep to filter the required pattern, and feed that output to xargs for removal.
find . -regextype sed -regex '.*[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}' -type d -mtime +365 | grep -E 'log*|dummy_*' | xargs rm -rf
I suggest that you please check the output of the earlier command first to ensure that only required files are deleted.

Retrieve specific files under a directory in Linux

I want to see the list of specific files under the directory using linux.
Say for example:-
I have following sub-directories in my current directory
Feb 16 00:37 a1
Feb 16 00:38 a2
Feb 16 00:36 a3
Now if i do ls a* - I can see
bash-4.1$ ls a*
a:
a1:
123.sh 123.txt
a2:
a234.sh a234.txt
a3:
a345.sh a345.txt
I want to filter out only .sh files from the directory so that output should be:-
a1:
123.sh
a2:
a234.sh
a3:
a345.sh
Is it Possible?
Moreover is it possible to print the 1st line of sh file also?
The following find command should work for you:
find . -maxdepth 2 -mindepth 2 -path '*/a*/*.sh' -print -exec head -n1 {} \;
Just take a look at those options. I hope you would find what you you are looking for
basic 'find file' commands
find / -name foo.txt -type f -print # full command
find / -name foo.txt -type f # -print isn't necessary
find / -name foo.txt # don't have to specify "type==file"
find . -name foo.txt # search under the current dir
find . -name "foo.*" # wildcard
find . -name "*.txt" # wildcard
find /users/al -name Cookbook -type d # search '/users/al'
search multiple dirs
find /opt /usr /var -name foo.scala -type f # search multiple dirs
case-insensitive searching
find . -iname foo # find foo, Foo, FOo, FOO, etc.
find . -iname foo -type d # same thing, but only dirs
find . -iname foo -type f # same thing, but only files
find files with different extensions
find . -type f \( -name "*.c" -o -name "*.sh" \) # *.c and *.sh files
find . -type f \( -name "*cache" -o -name "*xml" -o -name "*html" \) # three patterns
find files that don't match a pattern (-not)
find . -type f -not -name "*.html" # find allfiles not ending in ".html"
find files by text in the file (find + grep)
find . -type f -name "*.java" -exec grep -l StringBuffer {} \; # find StringBuffer in all *.java files
find . -type f -name "*.java" -exec grep -il string {} \; # ignore case with -i option
find . -type f -name "*.gz" -exec zgrep 'GET /foo' {} \; # search for a string in gzip'd files
Only using ls, you can get the .sh files and their parent directory with:
ls -1 * | grep ":\|.sh" | grep -B1 .sh
Which will provide the output:
a1:
123.sh
a2:
a234.sh
a3:
a345.sh
However, note that this won't have the correct behavior in case of you have any file called for example 123.sh.txt
In order to print the first line of the first .sh file in every folder:
head -n1 $(ls -1 */*.sh)
Yes and very easy and simple just with ls itself:
ls -d */*.sh
Prove
If you would like to print it with newline:
t $ ls -d */*.sh | tr ' ' '\n'
d1/file.sh
d2/file.sh
d3/file.sh
Or
ls -d */*.sh | tr '/' '\n'
the output:
d1
file.sh
d2
file.sh
d3
file.sh
Also for the first line if you want:
t $ ls -d */*.sh | tr ' ' '\n' | head -n 1
d1/file.sh

issue Find command Linux

I have a folder and I want count all regular files in it, and for this I use this bash command:
find pathfolder -type f 2> err.txt | wc -l
In the folder there are 3 empty text files and a subfolder with inside it other text files.
For this reason I should get 3 as a result, but I get 6 and I don't understand why. Maybe there is some options that I did not set.
If I remove the subfolder I get 4 as result
To grab all the files and directories in current directory without dot files:
shopt -u dotglob
all=(*)
To grab only directories:
dirs=(*/)
To count only non-dot files in current directory:
echo $(( ${#all[#]} - ${#dirs[#]} ))
To do this with find use:
find . -type f -maxdepth 1 ! -name '.*' -exec printf '%.0s.\n' {} + | wc -l
Below solutions ignore the filenames starting with dot.
To count the files in pathfolder only:
find pathfolder -maxdepth 1 -type f -not -path '*/\.*' | wc -l
To count the files in ALL child directories of pathfolder:
find pathfolder -mindepth 2 -maxdepth 2 -type f -not -path '*/\.*' | wc -l
UPDATE: Converting comments into an answer
Based on the suggestions received from anubhava, by creating a dummy file using the command touch $'foo\nbar', the wc -l counts this filename twice, like in below example:
$> touch $'foo\nbar'
$> find . -type f
./foo?bar
$> find . -type f | wc -l
2
To avoid this, get rid of the newlines before calling wc (anubhava's solution):
$> find . -type f -exec printf '%.0sbla\n' {} +
bla
$> find . -type f -exec printf '%.0sbla\n' {} + | wc -l
1
or avoid calling wc at all:
$> find . -type f -exec sh -c 'i=0; for f; do ((i++)); done; echo $i' sh {} +
1

How to prepend filename to last lines of files found through find command in Unix

I have a requirement where I need to display the last lines of all the files under a directory in the format
filename: lastline
I found the following code
find /user/directory/* -name "*txt" -mtime 0 -type f -exec awk '{s=$0};END{print FILENAME, ": ",s}' {} \;
But I read this reads the entire file each time. The files in my directory are huge so I cannot afford this. Do I have any alternatives?
find /user/directory/* -name "*txt" -mtime 0 -type f | while IFS= read -r file
do
echo -n "$file: "
tail -1 "$file"
done
The important change is that tail -1 won't read the whole file, but reads small portions from the end and increases them until it has found the complete last line.
If you know the directory name:
for f in $(/bin/ls directory/*.txt); do
echo "$f: $(tail -1 $f)"
done
will do the trick. More generally,
for f in $(find /user/directory -type f -name "*.txt"); do
echo "$f: $(tail -1 $f)"
done
will work as well. The program tail will start reading the file from the end, and tail -n will only read the last n lines of a specified file.
Using tail as in the other answers is good. Now, you can wrap all this into the find command.
If your find supports the -printf command:
find /user/directory/ -name "*txt" -mtime 0 -type f -printf '%p: ' -exec tail -1 {} \;
If your find doesn't support the -printf command:
find /user/directory/ -name "*txt" -mtime 0 -type f -exec printf '%s: ' {} \; -exec tail -1 {} \;

List all directories that contain a file with a particular extension

I need list all the directories that contain a file with .info extension in the first level.
--contrib
--abc
--ab.info
--def
--de.info
--xyz
--ab.gh
--ab.ij
The command should list
abc, def
This should work if you run it from your contrib directory:
find . -maxdepth 2 -name "*.info" -exec dirname {} \;
It will need more tweaking if you actually want to run it from the parent of contrib.
The above will give you:
./abc
./def
Which is not exactly what you wanted. So maybe something more like this will help:
find . -maxdepth 2 -name "*.info" -exec sh -c 'F=$(dirname {}) ; basename $F' \;
It is more convoluted but the result is:
abc
def
Or without basename and dirname:
find . -maxdepth 2 -name "*.info" -exec bash -c '[[ {} =~ .*/(.*)/.* ]] && echo ${BASH_REMATCH[1]}' \;
Or with sed:
find . -maxdepth 2 -name "*.info" -exec echo {} + | sed 's|./\(\S*\)/\S*|\1,|g'
Result:
abc, def,

Resources