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

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.

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

Delete files that don't match a particular string format

I have a set of files that are named similarly:
TEXT_TEXT_YYYYMMDD
Example file name:
My_House_20170426
I'm trying to delete all files that don't match this format. Every file should have a string of text followed by an underscore, followed by another string of text and another underscore, then a date stamp of YYYYMMDD.
Can someone provide some advice on how to build a find or a remove statement that will delete files that don't match this format?
Using find, add -delete to the end once you're sure it works.
# gnu find
find . -regextype posix-egrep -type f -not -iregex '.*/[a-z]+_[a-z]+_[0-9]{8}'
# OSX find
find -E . -type f -not -iregex '.*/[a-z]+_[a-z]+_[0-9]{8}'
Intentionally only matching alphabetical characters for TEXT. Add 0-9 to each TEXT area like this [a-z0-9] if you need numbers.
grep -v '(pattern)'
will filter out lines that match a pattern, leaving those that don't match. You might try piping in the output of ls. And if you're particularly brave, you could pipe the output to something like xargs rm. But deleting is kinda scary, so maybe save the output to a file first, look at it, then delete the files listed.

Linux terminal: Recursive search for string only in files w given file extension; display file name and absolute path

I'm new to Linux terminal; using Ubuntu Peppermint 5.
I want to recursively search all directories for a given text string (eg 'mystring'), in all files which have a given file extension (eg. '*.doc') in the file name; and then display a list of the file names and absolute file paths of all matches. I don't need to see any lines of content.
This must be a common problem. I'm hoping to find a solution which does the search quickly and efficiently, and is also simple to remember and type into the terminal.
I've tried using 'cat', 'grep', 'find', and 'locate' with various options, and piped together in different combinations, but I haven't found a way to do the above.
Something similar was discussed on:
How to show grep result with complete path or file name
and:
Recursively search for files of a given name, and find instances of a particular phrase AND display the path to that file
but I can't figure a way to adapt these to do the above, and would be grateful for any suggestions.
According to the grep manual, you can do this using the --include option (combined with the -l option if you want only the name — I usually use -n to show line numbers):
--include=glob
Search only files whose name matches glob, using wildcard matching as described under --exclude.
-l
--files-with-matches
Suppress normal output; instead print the name of each input file from which output would normally have been printed. The scanning of each file stops on the first match. (-l is specified by POSIX.)
A suitable glob would be "*.doc" (ensure that it is quoted, to allow the shell to pass it to grep).
GNU grep also has a recursive option -r (not in POSIX grep). Together with the globbing, you can search a directory-tree of ".doc" files like this:
grep -r -l --include="*.doc" "mystring" .
If you wanted to make this portable, then find is the place to start. But using grep's extension makes searches much faster, and is available on any Linux platform.
find . -name '*.doc' -exec grep -l 'mystring' {} \; -print
How it works:
find searches recursively from the given path .
for all files which name is '*.doc'
-exec grep execute grep on files found
suppress output from grep -l
and search inside the files for 'mystring'
The expression for grep ends with the {} \;
and -print print out all names where grep founds mystring.
EDIT:
To get only results from the current directory without recursion you can add:
-maxdepth 0 to find.

Problem using 'find' in BASH

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

How do I write a bash script to replace words in files and then rename files?

I have a folder structure, as shown below:
I need to create a bash script that does 4 things:
It searches all the files in the generic directory and finds the string 'generic' and makes it into 'something'
As above, but changes "GENERIC" to "SOMETHING"
As above, but changes "Generic" to "Something"
Renames any filename that has "generic" in it with "something"
Right now I am doing this process manually by using the search and replace in net beans. I dont know much about bash scripting, but i'm sure this can be done. I'm thinking of something that I would run and it would take "Something" as the input.
Where would I start? what functions should I use? overall guidance would be great. thanks.
I am using Ubuntu 10.5 desktop edition.
Editing
The substitution part is a sed script - call it mapname:
sed -i.bak \
-e 's/generic/something/g' \
-e 's/GENERIC/SOMETHING/g' \
-e 's/Generic/Something/g "$#"
Note that this will change words in comments and strings too, and it will change 'generic' as part of a word rather than just the whole word. If you want just the word, then you use end-word markers around the terms: 's/\<generic\>/something/g'. The -i.bak creates backups.
You apply that with:
find . -type f -exec mapname {} +
That creates a command with a list of files and executes it. Clearly, you can, if you prefer, avoid the intermediate mapname shell/sed script (by writing the sed script in place of the word mapname in the find command). Personally, I prefer to debug things separately.
Renaming
The renaming of the files is best done with the rename command - of which there are two variants, so you'll need to read your manual. Use one of these two:
find . -name '*generic*' -depth -exec rename generic something {} +
find . -name '*generic*' -depth -exec rename s/generic/something/g {} +
(Thanks to Stephen P for pointing out that I was using a more powerful Perl-based variant of rename with full Perl regexp capacity, and to Zack and Jefromi for pointing out that the Perl one is found in the real world* too.)
Notes:
This renames directories.
It is probably worth keeping the -depth in there so that the contents of the directories are renamed before the directories; you could otherwise get messages because you rename the directory and then can't locate the files in it (because find gave you the old name to work with).
The more basic rename will move ./generic/do_generic.java to ./something/do_generic.java only. You'd need to run the command more than once to get every component of every file name changed.
* The version of rename that I use is adapted from code in the 1st Edition of the Camel book.
Steps 1-3 can be done like this:
find .../path/to/generic -type f -print0 |
xargs -0 perl -pi~ -e \
's/\bgeneric\b/something/g;
s/\bGENERIC\b/SOMETHING/g;
s/\bGeneric\b/Something/g;'
I don't understand exactly what you want to happen in step 4 so I can't help with that part.

Resources