Problem using 'find' in BASH - linux

I'm following this guide to get some basic skills in Linux.
At the exercises of chapter 3 section, there are two exercises:
*Change to your home directory. Create a new directory and copy all
the files of the /etc directory into it. Make sure that you also copy
the files and directories which are in the subdirectories of /etc!
(recursive copy)
*Change into the new directory and make a directory for files starting
with an upper case character and one for files starting with a lower
case character. Move all the files to the appropriate directories. Use
as few commands as possible.
The first part was simple but I have encountered problems in the second part (although I thought it should be simple as well).
I did the first part successfully - that is, I have a copy of the /etc folder in ~/newetc - with all the files copied recursively into subdirectories.
I've created ~/newetc/upper and ~/newetc/lower directories.
My intention was to do something like mv 'find ... ' ./upper for example.
But first I thought I should make sure that I can find all the files with Upper/Lower case seperately. At this I failed.
I thought that find ~/newetc [A-Z].* (also tried: find ~/newetc -name [A-Z].*) to find all the upper case files - but it simply returns no results.
What's even stranger: find ~/newetc -name [a-z].*) returns only two files, although of course there are a lot more then that...
any idea what am I doing wrong?
Thank you for your time!
Edit: (I have tried to read the Man for find command btw, but didn't come up with anything)

The -name argument does not take a full regular expression by default. So [A-Z].* will match only if the second character is a dot.
Use the expression [A-Z]*, or use -regex and -regextype to match using a real regex.

You need to use quotes
find ~/new_etc -name "[A-Z]*"
find ~/new_etc -name "[a-z]*"

If you want to use regexp, then you must use -regex (or -iregex).

For finding stuff, the other answers tell you how to do it.
For moving the results of find, use the -exec flag (while being in newetc):
find -name "[A-Z]*" -exec mv {} upper/{} \;
find -name "[a-z]*" -exec mv {} lower/{} \;

The -name parameter takes a glob, not a regular expression (those are both very useful pages). So the dot does not have a special meaning for this parameter - It is interpreted as a literal dot character. Also, in a regular expression the * means "0 or more of the previous expression" while in a glob it means "any number of any character." So, as others have pointed out, the following should get you any files below the current directory which start with an uppercase character:
find . -name '[A-Z]*'

If you want to find all the name beginning with a capital letter you have to use
find . -name "[A-Z]*"
NOT
find [A-Z].*
otherwise yo will try to locate all the file that begin with a capital letter and have a . just after

Related

How to ignore a file using find command

I'm trying to find artifact using the command
name: Get path to Java artifact
run:echo JAVA_ARTIFACT=$(findbuild/libs/*.jar -type f) >>$GITHUB_ENV
The problem is I have 2 artifacts in that directory
build/libs/abc.jar
build/libs/abc-plain.jar
I want to pick only abc.jar file.
Can anyone suggest how can I achieve this ?
The find command can be used with regular expressions which makes it easy to get any kind of complex search results. How it works:
You have to use your find command with -regex instead of -name.
You have to generate a matching regular expression
How find passes the filename to the regular expression?
Assume we have the following directory structure:
/home/someone/build/libs/abc.jar
/home/someone/build/libs/abc-plain.jar
and we are sitting in someone
if we execute find . without any further arguments, we get:
./build/libs/abc.jar
./build/libs/abc-plain.jar
So we can for example search with regex for:
something starts with a single dot .
may have some additional path inside the file name
should NOT contain the - character in any number of character
ends with .jar
This results in:
'.'
'/*'
'[^-]+'
'.jar'
And all together:
find . -regex '.*/[^-]+.jar'
or if you ONLY want to search in build/libs/
find ./build/libs -regex '.*/[^-]+.jar'
You find a online regex tool there.
The find command support standard UNIX regex to match, include or exclude files. You can write complex queries easily with regex while finding the command recursively descends the directory tree for each /file/to/path listed, evaluating an expression.
Since you haven't clearly mentioned that you don't want the hyphen - in the filename, I'm assuming to find files without -.
I would try something like this. Matching lower-case, upper-case, numerical & .jar extension with regex.
find build/libs/ -regextype posix-egrep -regex '.*/[a-zA-Z0-9]+\.jar'
I got below output when tested locally.
touch abc.jar
touch abc-plain.jar
find . -regextype posix-egrep -regex '.*/[a-zA-Z0-9]+\.jar'
./abc.jar
You can try above commands here

Renaming files with its path name to proper case using zmv or rename

How can I convert all filenames and leading directories to titlecase (proper case) using zmv command.
One starting example I found to start with at :
autoload -U zmv
zmv '(*).(*)' '${(C)1}.$2'
The above would not work if files are within subdirectories.
or here
zmv '(**/)(*)~CVS~**/CVS' '${(C)1}${(L)2}'
To recursively lowercase files and directories where the name is not CVS. This one would try to copy lower-case filenames to title-case directories (which does not yet exist and hence would not work either.
The following works well if trying to convert to lowercase (from https://stackoverflow.com/a/152741/631775):
find my_root_dir -depth -exec rename 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \;
But I would like to make proper case here.
This uses the Perl-script version of rename AKA prename.
find -depth -execdir rename 's/.*/\L$&/;s/[[:lower:]]+/\u$&/g' {} \;
It lowercases the whole name then uppercases the first letter of each word.
Since all characters are already lowercase after the first s command, [[:lower:]]+ could be simplified to .+.
Note that "word" is loosely defined, for example, "ABC6DEF7GHI" would become "Abc6Def7Ghi".
The following seemed to work for me :
zmv '(**/)(*)(**)' '$1${(C)2}'
Is there any issue in using this, some outlier case which is not covered. Also could someone please provide a rename version as well if possible.

Using Perl how can I clean up left over directories with no files?

There is a specific directory which is used as a temp/scratch directory by some program.
E.g. /a/b/c/work
Under work multiple hierarchical directories may exist e.g.
/a/b/c/work/
\d1/
\d1.1
\d2
\d2.2
What I want is to clean up this work directory as there are left over files that take space.
Essentially I need to delete all subdirectories under work that the leaf directory is empty.
So if d1.1 is empty but d2.2 has files then delete everything under d1 (including d1) but not d2.
What is the cleanest/standard way to do this in perl?
I thought to use a solution with backticks e.g. rm -rf etc but I thought there could be some better way than coding sequences of ls folowed by rm
Note: Just to be clear. I want a solution in Perl as this is not a one time thing and I dont want to do this manually each time
If you use find command this way you can achieve it.
find /path/to/dir -empty -type d -delete
Where,
-empty Only find empty files and make sure it is a regular file or a directory.
-type d Only match directories.
-delete Delete files.
Always put -delete option at the end of find command as find command line is evaluated as an expression, so putting -delete first will make find try to delete everything below the starting points you specified.
To automate this in shell script follow below code:
path=`pwd`
find $path -empty -type d -delete
or you can give certain input as arguments of shell script like myShell.sh /path/to/mydir in that case the following code will be do the work,
$path=$1
find $path -empty -type d -delete
As for if you really want to go for perl you can find your answer as follows
use strict;
use warnings;
use File::Util;
my $path = '...';
my $fu = File::Util->new();
my #all_dirs = $fu->list_dir($path, '--recurse', '--dirs-only');
my #empty_dirs = grep { not $fu->list_dir($_) } #all_dirs;
also a short method
perl -MFile::Find -e"finddepth(sub{rmdir},'.')"
which is explained very good here.

Using Perl-based rename command with find in Bash

I just stumbled upon Perl today while playing around with Bash scripting. When I tried to remove blank spaces in multiple file names, I found this post, which helped me a lot.
After a lot of struggling, I finally understand the rename and substitution commands and their syntax. I wanted to try to replace all "_(x)" at the end of file names with "x", due to duplicate files. But when I try to do it myself, it just does not seem to work. I have three questions with the following code:
Why is nothing executed when I run it?
I used redirection to show me the success note as an error, so I know what happened. What did I do wrong about that?
After a lot of research, I still do not entirely understand file descriptors and redirection in Bash as well as the syntax for the substitute function in Perl. Can somebody give give me a link for a good tutorial?
find -name "*_(*)." -type f | \
rename 's/)././g' && \
find -name "*_(*." -type f | \
rename 's/_(//g' 2>&1
You either need to use xargs or you need to use find's ability to execute commands:
find -name "*_(*)." -type f | xargs rename 's/)././g'
find -name "*_(*." -type f | xargs rename 's/_(//g'
Or:
find -name "*_(*)." -type f -exec rename 's/)././g' {} +
find -name "*_(*." -type f -exec rename 's/_(//g' {} +
In both cases, the file names are added to the command line of rename. As it was, rename would have to read its standard input to discover the file names — and it doesn't.
Does the first find find the files you want? Is the dot at the end of the pattern needed? Do the regexes do what you expect? OK, let's debug some of those too.
You could do it all in one command with a more complex regex:
find . -name "*_(*)" -type f -exec rename 's/_\((\d+)\)$/$1/' {} +
The find pattern is corrected to lose the requirement of a trailing .. If the _(x) is inserted before the extension, then you'd need "*_(*).*" as the pattern for find (and you'll need to revise the Perl regexes).
The Perl substitute needs dissection:
The \( matches an open parenthesis.
The ( starts a capture group.
The \d+ looks for 'one or more digits'.
The ) stops the capture group. It is the first and only, so it is given the number 1.
The \) matches a close parenthesis.
The $ matches the end of the file name.
The $1 in the replacement puts the value of capture group 1 into the replacement text.
In your code, the 2>&1 sent the error messages from the second rename command to standard output instead of standard error. That really doesn't help much here.
You need two separate tutorials; you are not going to find one tutorial that covers I/O redirection in Bash and regular expressions in Perl.
The 'official' Perl regular expression tutorial is:
perlretut, also available as perldoc perlretut on your machine.
The Bash manual covers I/O redirection, but it is somewhat terse:
I/O Redirections.

Find files with a certain extension that exceeds a certain file size

I'm having trouble with the find command in bash.
I'm trying to find a file that ends with .c and has a file size bigger than 2000 bytes. I thought it would be:
find $HOME -type f -size +2000c .c$
But obviously that isn't correct.
What am I doing wrong?
find $HOME -type f -name "*.c" -size +2000c
Have a look to the -name switch in the mane page:
-name pattern
Base of file name (the path with the leading directories
removed) matches shell pattern pattern. The metacharacters
(`*', `?', and `[]') match a `.' at the start of the base name
(this is a change in findutils-4.2.2; see section STANDARDS CON‐
FORMANCE below). To ignore a directory and the files under it,
use -prune; see an example in the description of -path. Braces
are not recognised as being special, despite the fact that some
shells including Bash imbue braces with a special meaning in
shell patterns. The filename matching is performed with the use
of the fnmatch(3) library function. Don't forget to enclose
the pattern in quotes in order to protect it from expansion by
the shell.
Note the suggestion at the end to always enclose the pattern inside quotes. The order of the options is not relevant. Have, again, a look to the man page:
EXPRESSIONS
The expression is made up of options (which affect overall operation
rather than the processing of a specific file, and always return true),
tests (which return a true or false value), and actions (which have
side effects and return a true or false value), all separated by opera‐
tors. -and is assumed where the operator is omitted.
If the expression contains no actions other than -prune, -print is per‐
formed on all files for which the expression is true.
So, options are, by default, connected with and -and operator: they've to be all true in order to find a file and the order doesn't matter at all. The order could be relevant only for more complicated pattern matching where there are other operators than -and.
Try this:
find $HOME -type f -size +2000c -name *.c
Try the following:
find $HOME -type f -size +2000c -name *.c

Resources