ZSH: rename files in nested folders, capitalize specific letters - terminal

I have many nested folders of json language files, such as
da-dk.json
de-de.json
en-us.json
I need to change them all to capitalize the letters after the hyphen, as in
da-DK.json
de-DE.json
en-US.json
I am on a Mac with zsh. I originally thought I could do it with a GUI utility I have used called A Better Finder Rename but it apparently does not offer case conversions on replace.
I know regex and figured it would be something like find
^([a-z]{2})-([a-z]{2}) and replace with $1-\U$2 but I'm not sure how to do this in the command line.

Given that you are using ZSH shell, you can use the awesome zmv command
zmv '(**/)(*)-(*).json' '${1}${2}-$3:u.json'
You may need to autoload zmv before running the above command.
Short explanation:
(**/) takes care of nested folders which is mapped to ${1}
First (*) matches the part before hyphen and is mapped to ${2}
Second (*) matches the part after hyphen and is uppercased by :u before being mapped to ${3}.
There are some useful material in this SO question and its answers.

In traditional shell commands:
for i in *.json; do
echo mv "$i" "${i:0:3}$(tr '[[:lower:]]' '[[:upper:]]' <<< ${i:3:2}).json"
done
Drop echo when the output looks good.

With perl rename:
install via homebrew (if not already installed):
brew install rename
command:
rename -n 's/\w{2}(?=\.)/uc $&/e' *.json
Drop -n switch when the output looks good.

Related

One-line way to remove last n characters from string

I have a bunch of files in the format photo.jpg.png in a folder, and for every photo in this folder, I want to replace the .jpg.png with .png. How can I do this from Terminal?
I have a basic Python and bash background, so I know I'd want to do something like this:
$ for i in *.png; do mv $i $i[:-8]; done
$ for i in *; do mv $i $i.png; done
But what would I replace the Pythonic [:-8] with in order to remove the last 8 characters of each filename?
EDIT I now realize that a substring that counts from the end of the string would be superior. Is there a way to do this as well?
You can use pattern expansion:
for f in *.jpg.png; do mv -v "$f" "${f/.jpg.png/.png}"; done
Though you might still have problems with a filename like foo.jpg.png.gif.
If you really want to strip the last 7 or 8 characters, you can use a substring expansion:
for f in *.jpg.png; do mv -v "$f" "${f:0:-7}png"; done
Note that use of negative numbers in substring length requires bash version 4 or higher.
With Perl's standalone rename command:
rename -n 's/jpg\.png$/png/' *.jpg.png
or
rename -n 's/.......$/png/' *.jpg.png
Output:
rename(photo.jpg.png, photo.jpg)
If everything looks okay, remove `-n'.
rename is designed for this kinda thing;
$ rename .jpg.png .png *.jpg.png
For MacOS, I realized that rename may not be available by default, you can install it using brew.
$ brew install rename
and then use -s option for rename;
$ rename -s .jpg.png .png *.jpg.png
Removing the last 8 characters using perl's rename :
$ rename -n 's/.{8}$//' *.png
(remove -n switch when your tests are OK)
or with bash :
for i in *.png; do
echo mv "$i" "${i:0:-8}"
done
(remove echo when your tests are OK)
There are other tools with the same name which may or may not be able to do this, so be careful.
If you run the following command (GNU)
$ file "$(readlink -f "$(type -p rename)")"
and you have a result like
.../rename: Perl script, ASCII text executable
and not containing:
ELF
then this seems to be the right tool =)
If not, to make it the default (usually already the case) on Debian and derivative like Ubuntu :
$ sudo update-alternatives --set rename /path/to/rename
(replace /path/to/rename to the path of your perl's rename command.
If you don't have this command, search your package manager to install it or do it manually
Last but not least, this tool was originally written by Larry Wall, the Perl's dad.
Something like that:
$ v=test.jpg.png
$ echo ${v:0:-8}
test
This should work:
for file in *.jpg.png; do mv $file ${file//jpg.png/jpg} ; done

How to remove unknown file extensions from files using script

I can remove file extensions if I know the extensions, for example to remove .txt from files:
foreach file (`find . -type f`)
mv $file `basename $file .txt`
end
However if I don't know what kind of file extension to begin with, how would I do this?
I tried:
foreach file (`find . -type f`)
mv $file `basename $file .*`
end
but it wouldn't work.
What shell is this? At least in bash you can do:
find . -type f | while read -r; do
mv -- "$REPLY" "${REPLY%.*}"
done
(The usual caveats apply: This doesn't handle files whose name contains newlines.)
You can use sed to compute base file name.
foreach file (`find . -type f`)
mv $file `echo $file | sed -e 's/^\(.*\)\.[^.]\+$/\1/'`
end
Be cautious: The command you seek to run could cause loss of data!
If you don't think your file names contain newlines or double quotes, then you could use:
find . -type f -name '?*.*' |
sed 's/\(.*\)\.[^.]*$/mv "&" "\1"/' |
sh
This generates your list of files (making sure that the names contain at least one character plus a .), runs each file name through the sed script to convert it into an mv command by effectively removing the material from the last . onwards, and then running the stream of commands through a shell.
Clearly, you test this first by omitting the | sh part. Consider running it with | sh -x to get a trace of what the shell's doing. Consider making sure you capture the output of the shell, standard output and standard error, into a log file so you've got a record of the damage that occurred.
Do make sure you've got a backup of the original set of files before you start playing with this. It need only be a tar file stored in a different part of the directory hierarchy, and you can remove it as soon as you're happy with the results.
You can choose any shell; this doesn't rely on any shell constructs except pipes and single quotes and double quotes (pretty much common to all shells), and the sed script is version neutral too.
Note that if you have files xyz.c and xyz.h before you run this, you'll only have a file xyz afterwards (and what it contains depends on the order in which the files are processed, which needn't be alphabetic order).
If you think your file names might contain double quotes (but not single quotes), you can play with the changing the quotes in the sed script. If you might have to deal with both, you need a more complex sed script. If you need to deal with newlines in file names, then it is time to (a) tell your user(s) to stop being silly and (b) fix the names so they don't contain newlines. Then you can use the script above. If that isn't feasible, you have to work a lot harder to get the job done accurately — you probably need to make sure you've got a find that supports -print0, a sed that supports -z and an xargs that supports -0 (installing the most recent GNU versions if you don't already have the right support in place).
It's very simple:
$ set filename=/home/foo/bar.dat
$ echo ${filename:r}
/home/foo/bar
See more in man tcsh, in "History substitution":
r
Remove a filename extension '.xxx', leaving the root name.

automatically renaming files

I have a bunch of files (more than 1000) on this like the followings
$ ls
org.allenai.ari.solvers.termselector.BaselineLearnersurfaceForm-dev.lc
org.allenai.ari.solvers.termselector.BaselineLearnersurfaceForm-dev.lex
org.allenai.ari.solvers.termselector.BaselineLearnersurfaceForm-train.lc
org.allenai.ari.solvers.termselector.BaselineLearnersurfaceForm-train.lex
org.allenai.ari.solvers.termselector.BaselineLearnersurfaceForm.lc
org.allenai.ari.solvers.termselector.BaselineLearnersurfaceForm.lex
org.allenai.ari.solvers.termselector.ExpandedLearner.lc
org.allenai.ari.solvers.termselector.ExpandedLearner.lex
org.allenai.ari.solvers.termselector.ExpandedLearnerSVM.lc
org.allenai.ari.solvers.termselector.ExpandedLearnerSVM.lex
....
I have to rename these files files by adding a learners right before the capitalized name. For example
org.allenai.ari.solvers.termselector.BaselineLearnersurfaceForm.lex
would change to
org.allenai.ari.solvers.termselector.learners.BaselineLearnersurfaceForm.lex
and this one
org.allenai.ari.solvers.termselector.ExpandedLearner.lc
would change to
org.allenai.ari.solvers.termselector.learners.ExpandedLearner.lc
Any ideas how to do this automatically?
for f in org.*; do
echo mv "$f" "$( sed 's/\.\([A-Z]\)/.learner.\1/' <<< "$f" )"
done
This short loop outputs an mv command that renames the files in the manner that you wanted. Run it as-is first, and when you are certain it's doing what you want, remove the echo and run again.
The sed bit in the middle takes a filename ($f, via a here-string, so this requires bash) and replaces the first occurrence of a capital letter after a dot with .learner. followed by that same capital letter.
There is a tool called perl-rename, sometimes rename. Not to be confused with rename from util-linux.
It's very good for tasks like this as it takes a perl expression and renames accordingly:
perl-rename 's/(?=\.[A-Z])/.learners/' *
You can play with the regex online
Alternative you can a for loop and $BASH_REMATCH:
for file in *; do
[ -e "$file" ] || continue
[[ "$file" =~ ^([^A-Z]*)(.*)$ ]]
mv -- "$file" "${BASH_REMATCH[1]}learners.${BASH_REMATCH[2]}"
done
A very simple approach (useful if you only need to do this one time) is to ls >dummy them into a text file dummy, and then use find/replace in a text editor to make lines of the form mv xxx.yyy xxx.learners.yyy. Then you can simple execute the resulting file with ./dummy.
The exact find/replace commands depend on the text editor you use, but something like
replace org. with mv org.. That gets you the mv in the beginning.
replace mv org.allenai.ari.solvers.termselector.$1 with mv org.allenai.ari.solvers.termselector.$1 org.allenai.ari.solvers.termselector.learner.$1 to duplicate the filename and insert the learner.
There is also syntax with a for, which can do it probably in one line, (long) but I cannot explain it - try help for if you want to learn about it.

Renaming multiple files in Linux/Unix

I have over a thousand files of similar names in a directory and wish to do a rename. The files are of this format
GW_LGMS01-50160306185154-01375272.CDR
GW_LGMS01-50160306237154-01375272.CDR.00001
GW_LGMS02-50160306133554-02308872.CDR
GW_LGMS02-50160306137554-02308872.CDR.00014
GW_LGMS03-50160306221836-02217475.CDR.00001
GW_LGMS03-50160306235132-02217475.CDR
I want to do a rename on all of them at once to append a 0- before 50160306 on all of them. That is,
GW_LGMS01-0-50160306185154-01375272.CDR
GW_LGMS01-0-50160306237154-01375272.CDR.00001
GW_LGMS02-0-50160306133554-02308872.CDR
GW_LGMS02-0-50160306137554-02308872.CDR.00014
GW_LGMS03-0-50160306221836-02217475.CDR.00001
GW_LGMS03-0-50160306235132-02217475.CDR
50160306 is what all the files have in common.
Assuming that -50160306 is unique in the file names, and that you are using a shell that understands ${parameter/pattern/string} (Bash, KornShell, etc.):
for f in *.CDR*; do
echo mv "$f" "${f/-50160306/-0-50160306}"
done
Do this with the echo in place to see what would happen, then remove the echo when you are sure it does the right thing.
If you are afraid to mess up, just put the files with the new names in a new folder:
mkdir renamed
for f in *.CDR*; do
cp "$f" renamed/"${f/-50160306/-0-50160306}"
done
If you don't use bash:
#!/bin/sh
for i in * ; do
mv "$i" "$(printf '%s' "$i" | sed 's/\(50160306.*\)/0-\1/')"
done
There are two rename tools floating around: one is part of the util-linux package, the other is Perl based (see this answer for details). To find out which one you have, check at the end of the man page (man rename).
With the util-linux version, you can rename your files as follows:
rename 50160306 0-50160306 *
and for the Perl based version, it would be (untested!)
rename 's/50160306/0-$&/' *
Be aware that there are no safeguards with these commands – test them on a small sample before you use them.

How to Batch Rename Files in a macOS Terminal?

I have a folder with a series of files named:
prefix_1234_567.png
prefix_abcd_efg.png
I'd like to batch remove one underscore and the middle content so the output would be:
prefix_567.png
prefix_efg.png
Relevant but not completely explanatory:
How can I batch rename files using the Terminal?
Regex to batch rename files in OS X Terminal
In your specific case you can use the following bash command (bash is the default shell on macOS):
for f in *.png; do echo mv "$f" "${f/_*_/_}"; done
Note: If there's a chance that your filenames start with -, place -- before them[1]:
mv -- "$f" "${f/_*_/_}"
Note: echo is prepended to mv so as to perform a dry run. Remove it to perform actual renaming.
You can run it from the command line or use it in a script.
"${f/_*_/_}" is an application of bash parameter expansion: the (first) substring matching pattern _*_ is replaced with literal _, effectively cutting the middle token from the name.
Note that _*_ is a pattern (a wildcard expression, as also used for globbing), not a regular expression (to learn about patterns, run man bash and search for Pattern Matching).
If you find yourself batch-renaming files frequently, consider installing a specialized tool such as the Perl-based rename utility.
On macOS you can install it using popular package manager Homebrew as follows:
brew install rename
Here's the equivalent of the command at the top using rename:
rename -n -e 's/_.*_/_/' *.png
Again, this command performs a dry run; remove -n to perform actual renaming.
Similar to the bash solution, s/.../.../ performs text substitution, but - unlike in bash - true regular expressions are used.
[1] The purpose of special argument --, which is supported by most utilities, is to signal that subsequent arguments should be treated as operands (values), even if they look like options due to starting with -, as Jacob C. notes.
To rename files, you can use the rename utility:
brew install rename
For example, to change a search string in all filenames in current directory:
rename -nvs searchword replaceword *
Remove the 'n' parameter to apply the changes.
More info: man rename
You could use sed:
ls * | sed -e 'p;s#_.*_#_#g' | xargs -n2 mv
result:
prefix_567.png prefix_efg.png
*to do a dry-run first, replace mv at the end with echo
Explanation:
e: optional for only 1 sed command.
p: to print the input to sed, in this case it will be the original file name before any renaming
#: is a replacement of / character to make sed more readable. That is, instead of using sed s/search/replace/g, use s#search#replace#g
_.* : the underscore is an escape character to refer to the actual '.' character zero or more times (as opposed to ANY character in regex)
-n2: indicates that there are 2 outputs that need to be passed on to mv as parameters. for each input from ls, this sed command will generate 2 output, which will then supplied to mv.
I had a batch of files that looked like this: be90-01.png and needed to change the dash to underscore. I used this, which worked well:
for f in *; do mv "$f" "`echo $f | tr '-' '_'`"; done
you can install rename command by using brew. just do brew install rename and use it.
Using mmv
mmv '*_*_*' '#1_#3' *.png
try this
for i in *.png ; do mv "$i" "${i/remove_me*.png/.png}" ; done
Here is another way:
for file in Name*.png; do mv "$file" "01_$file"; done
Since programmatically renaming files is risky (potentially destructive if you get it wrong), I would use a tool with a dry run mode built specifically for bulk renaming, e.g. renamer.
This command operates on all files in the current directory, use --dry-run until you're confident the output looks correct:
$ renamer --find "/(prefix_)(\w+_)(\w+)/" --replace "$1$3" -e name --dry-run *
Dry run
✔︎ prefix_1234_567.png → prefix_567.png
✔︎ prefix_abcd_efg.png → prefix_efg.png
Rename complete: 2 of 2 files renamed.
Plenty more renamer usage examples here.

Resources