How to find directories without dot in bash? - bash

I try to find folders without dot symbol.
I Search it in users directory via this script:
!#/bin/bash
users=$(ls /home)
for user in $users;
do
find /home/$user/web/ -maxdepth 1 -type d -iname '*' ! -iname "*.*"
done
But I see in result users with folders with dot, for example - test.uk or test.cf
What I do wrong?
Thanks in advance!

You can use find with -regex option for that:
find /home/$user/web/ -maxdepth 1 -type d -regex '\./[^.]*$'
'\./[^.]*$' will match names without any DOT.

The problem is that your command finds directories in /home/username/web/ where the directory doesn't contain a dot.
It does not check to see if username itself contains a dot.
To see if there's a dot anywhere, you can use ipath instead of iname:
!#/bin/bash
users=$(ls /home)
for user in $users;
do
find /home/$user/web/ -maxdepth 1 -type d -iname '*' ! -ipath "*.*"
done
Or more correctly and succinctly:
#!/bin/bash
find /home/*/web/ -maxdepth 1 -type d ! -ipath "*.*"

No need for find; just use an extended glob to match any files not containing a .
shopt -s extglob
for dir in /home/*/;
do
printf '%s\n' "$dir"/!(*.*)
done
You could even do away with the loop entirely:
shopt -s extglob
printf '%s\n' /home/*/!(*.*)
To exclude directories in /home that contain a ., you can change /home/*/ to /home/!(*.*)/ in either example.

Related

find command - get base name only - NOT with basename command / NOT with printf

Is there any way to get the basename in the command find?
What I don't need:
find /dir1 -type f -printf "%f\n"
find /dir1 -type f -exec basename {} \;
Why you may ask? Because I need to continue using the found file. I basically want something like this:
find . -type f -exec find /home -type l -name "*{}*" \;
And it uses ./file1, not file1 as the agrument for -name.
Thanks for your help :)
If you've got Bash version 4.3 or later, try this Shellcheck-clean pure Bash code:
#! /bin/bash -p
shopt -s dotglob globstar nullglob
for path in ./**; do
[[ -L $path ]] && continue
[[ -f $path ]] || continue
base=${path##*/}
for path2 in /home/**/*"$base"*; do
[[ -L $path2 ]] && printf '%s\n' "$path2"
done
done
shopt -s ... enables some Bash settings that are required by the code:
dotglob enables globs to match files and directories that begin with .. find shows such files by default.
globstar enables the use of ** to match paths recursively through directory trees. globstar was introduced in Bash 4.0, but it was dangerous to use before Bash 4.3 (2014) because it followed symlinks when looking for matches.
nullglob makes globs expand to nothing when nothing matches (otherwise they expand to the glob pattern itself, which is almost never useful in programs).
See Removing part of a string (BashFAQ/100 (How do I do string manipulation in bash?)) for an explanation of ${path##*/}. That always works, even in some rare cases where $(basename "$path") doesn't.
See the accepted, and excellent, answer to Why is printf better than echo? for an explanation of why I used printf instead of echo to output the found paths.
This solution works correctly if you've got files that contain pattern characters (?, *, [, ], \) in their names.
Spawn a shell and make the second call to find from there
find /dir1 -type f -exec sh -c '
for p; do
find /dir2 -type l -name "*${p##*/}*"
done' sh {} +
If your files may contain special characters in their names (like [, ?, etc.), you may want to escape them like this to avoid false positives
find /dir1 -type f -exec sh -c '
for p; do
esc=$(printf "%sx\n" "${p##*/}" | sed "s/[?*[\]/\\\&/g")
esc=${esc%x}
find /dir2 -type l -name "*$esc*"
done' sh {} +
You'll have to forward it to another evaluator. There is no way to do that in find.
find . -type f -printf '%f\0' |
xargs -r0I{} find /home -type l -name '*{}*'
This answers your question about trying to merge the functionality of %f and -exec find and is based on your example but your example injects raw filenames as -name patterns so avoid that and look at other solutions instead.
Simply spawn a bash shell:
find /dir1 -type f -exec bash -c '
base=$(basename "$1")
echo "$base"
do_something_else "$base"
' bash {} \;
$1 in the bash part is each file filtered by find.

How to show only file name when searching subdirectories in bash

I'm trying to list all files, except hidden ones, in only the subdirectories of a folder in bash by doing:
$ find ./public -mindepth 3 -type f -not -path '*/\.*'
That returns:
./public/mobile/images/image1.jpg
./public/mobile/images/image2.png
./public/mobile/images/image3.jpg
./public/mobile/javascripts/java1.js
./public/mobile/javascripts/java2.js
./public/mobile/javascripts/java3.js
./public/mobile/stylesheets/main.css
./public/mobile/views/doc1.html
./public/mobile/views/doc2.html
./public/mobile/views/doc3.html
How can I ignore the file path and show only the file name with the extension?
Thank you :)
Use -printf additionally to the find command, instead of -print.
find ./public -mindepth 3 -type f -not -path '*/\.*' -printf %f\\n
Note the usage of \\n - you need \n to add a new line after file name, but add another \ as escape or add some quotes to prevent interpreting \n by shell
If you are using bash 4 or later, you can skip using find and using a file pattern instead.
shopt -s globstar # For **
printf "%s\n" public/*/*/**/*.*
If you expect some files to have no extension, you'll need to use a loop and filter out
non-file matches manually.
for f in */*/*/**/*; do
[[ -f $f ]] || continue
printf "%s\n" "${f##*/}"
done

find option available to omit leading './' in result

I think this is probably a pretty n00ber question but I just gotsta ask it.
When I run:
$ find . -maxdepth 1 -type f \( -name "*.mp3" -o -name "*.ogg" \)
and get:
./01.Adagio - Allegro Vivace.mp3
./03.Allegro Vivace.mp3
./02.Adagio.mp3
./04.Allegro Ma Non Troppo.mp3
why does find prepend a ./ to the file name? I am using this in a script:
fList=()
while read -r -d $'\0'; do
fList+=("$REPLY")
done < <(find . -type f \( -name "*.mp3" -o -name "*.ogg" \) -print0)
fConv "$fList" "$dBaseN"
and I have to use a bit of a hacky-sed-fix at the beginning of a for loop in function 'fConv', accessing the array elements, to remove the leading ./. Is there a find option that would simply omit the leading ./ in the first place?
The ./ at the beginning of the file is the path. The "." means current directory.
You can use "sed" to remove it.
find . -maxdepth 1 -type f \( -name "*.mp3" -o -name "*.ogg" \) | sed 's|./||'
I do not recommend doing this though, since find can search through multiple directories, how would you know if the file found is located in the current directory?
If you ask it to search under /tmp, the results will be on the form /tmp/file:
$ find /tmp
/tmp
/tmp/.X0-lock
/tmp/.com.google.Chrome.cUkZfY
If you ask it to search under . (like you do), the results will be on the form ./file:
$ find .
.
./Documents
./.xmodmap
If you ask it to search through foo.mp3 and bar.ogg, the result will be on the form foo.mp3 and bar.ogg:
$ find *.mp3 *.ogg
click.ogg
slide.ogg
splat.ogg
However, this is just the default. With GNU and other modern finds, you can modify how to print the result. To always print just the last element:
find /foo -printf '%f\0'
If the result is /foo/bar/baz.mp3, this will result in baz.mp3.
To print the path relative to the argument under which it's found, you can use:
find /foo -printf '%P\0'
For /foo/bar/baz.mp3, this will show bar/baz.mp3.
However, you shouldn't be using find at all. This is a job for plain globs, as suggested by R Sahu.
shopt -s nullglob
files=(*.mp3 *.ogg)
echo "Converting ${files[*]}:"
fConv "${files[#]}"
find . -maxdepth 1 -type f \( -name "*.mp3" -o -name "*.ogg" \) -exec basename "{}" \;
Having said that, I think you can use a simpler approach:
for file in *.mp3 *.ogg
do
if [[ -f $file ]]; then
# Use the file
fi
done
If your -maxdepth is 1, you can simply use ls:
$ ls *.mp3 *.ogg
Of course, that will pick up any directory with a *.mp3 or *.ogg suffix, but you probably don't have such a directory anyway.
Another is to munge your results:
$ find . -maxdepth 1 -type f \( -name "*.mp3" -o -name "*.ogg" \) | sed 's#^\./##'
This will remove all ./ prefixes, but not touch other file names. Note the ^ anchor in the substitution command.

Piping find to find

I want to pipe a find result to a new find. What I have is:
find . -iname "2010-06*" -maxdepth 1 -type d | xargs -0 find '{}' -iname "*.jpg"
Expected result: Second find receives a list of folders starting with 2010-06, second find returns a list of jpg's contained within those folders.
Actual result: "find: ./2010-06 New York\n: unknown option"
Oh darn. I have a feeling it concerns the format of the output that the second find receives as input, but my only idea was to suffix -print0 to first find, with no change whatsoever.
Any ideas?
You need 2 things. -print0, and more importantly -I{} on xargs, otherwise the {} doesn't do anything.
find . -iname "2010-06*" -maxdepth 1 -type d -print0 | xargs -0 -I{} find '{}' -iname '*.jpg'
Useless use of xargs.
find 2010-06* -iname "*.jpg"
At least Gnu-find accepts multiple paths to search in. -maxdepth and type -d is implicitly assumed.
How about
find . -iwholename "./2010-06*/*.jpg
etc?
Although you did say that you specifically want this find + pipe problem to work, its inefficient to fork an extra find command. Since you are specifying -maxdepth as 1, you are not traversing subdirectories. So just use a for loop with shell expansion.
for file in *2010-06*/*.jpg
do
echo "$file"
done
If you want to find all jpg files inside each 2010-06* folders recursively, there is also no need to use multiple finds or xargs
for directory in 2010-06*/
do
find $directory -iname "*.jpg" -type f
done
Or just
find 2006-06* -type f -iname "*.jpg"
Or even better, if you have bash 4 and above
shopt -s globstar
shopt -s nullglob
for file in 2010-06*/**/*.jpg
do
echo "$file"
done

Exclude a string from wildcard search in a shell

I am trying to exclude a certain string from a file search.
Suppose I have a list of files: file_Michael.txt, file_Thomas.txt, file_Anne.txt.
I want to be able and write something like
ls *<and not Thomas>.txt
to give me file_Michael.txt and file_Anne.txt, but not file_Thomas.txt.
The reverse is easy:
ls *Thomas.txt
Doing it with a single character is also easy:
ls *[^s].txt
But how to do it with a string?
Sebastian
You can use find to do this:
$ find . -name '*.txt' -a ! -name '*Thomas.txt'
With Bash
shopt -s extglob
ls !(*Thomas).txt
where the first line means "set extended globbing", see the manual for more information.
Some other ways could be:
find . -type f \( -iname "*.txt" -a -not -iname "*thomas*" \)
ls *txt |grep -vi "thomas"
If you are looping a wildcard, just skip the rest of the iteration if there is something you want to exclude.
for file in *.txt; do
case $file in *Thomas*) continue;; esac
: ... do stuff with "$file"
done

Resources