OSX / MacOs batch rename hexadecimal filenames to decimal filenames - bash

I want to rename filenames with a hexadecimal part in the name to decimal. For example: MOV12B.MOD, MOV12C.MOD etc. To MOV299.mod, MOV300.MOD.
Can this be done in terminal?
It is possible to rename the extension using:
find . -name "*.MOD" -exec rename 's/\.MOD$/.MPG/' '{}' \;
But how can I rename the files to decimal?

Sure, you can do it with rename, also known as Perl rename and prename which is most simply installed on macOS with homebrew using:
brew install rename
Then the command is:
rename --dry-run 's/[0-9A-F]+/hex($&)/e' *MOD
Sample Output
'MOV10.MOD' would be renamed to 'MOV16.MOD'
'MOV12B.MOD' would be renamed to 'MOV299.MOD'
'MOV12C.MOD' would be renamed to 'MOV300.MOD'
'MOVBEEF.MOD' would be renamed to 'MOV48879.MOD'
If you like what it does, remove the --dry-run part and do it for real.
I would recommend you make a backup before trying this anyway, because if your films are actually named "Film 23.MOD" rather than "MOV12B.MOD" you will get:
'Film 23.MOD' would be renamed to '15ilm 23.MOD'
If you want to put the date in too, you can do:
rename --dry-run 's/[0-9A-F]+/hex($&)/e; s|.MOD| 17/01/2018.MOD|' *MOD
Sample Output
'MOV12A.MOD' would be renamed to 'MOV298 17/01/2018.MOD'
Why couldn't you find it in the man-page? Well, there is a line in there that casually says you can pass a line of Perl code to modify the name. That means that the entire Perl language is available to you - so you could write several pages of code that access a database, run something on a remote machine, or fetch a URL in order to rename your file.
The only tricky thing in my code is the e lurking at the end:
s/search/replace/e
The e means that the second half of the search/replace is actually executed so it is not a straight textual replacement, it is a new program that gets the search string from the left-hand side in $& and can do maths or lookups on it.
I have done some other answers that involve similar techniques...
here,
here,
here.
If you want to put the modification time of the file into its name as well, you need to do a little more work. First, stat() the file before changing its name ;-) Remember you receive the original filename in $_. Then do the the hex to decimal thing, then add in the mtime. Remember Perl uses a dot to concatenate strings together.
So, the command is going to look like this:
rename --dry-run 'my $mtime=(stat($_))[9]; s/[0-9A-F]+/hex($&) . " " . $mtime/e;' *MOD
Sample Output
'MOV12A.MOD' would be renamed to 'MOV298 1516229449.MOD'
If all the substitution and evaluation gets too much, you can always do all your calculations and assign the result to Perl's $_ variable through which you receive the into filename and in which you pass the desired name back to rename. So, for an example:
rename --dry-run 'my $prefix="PREFIX "; my $middle=$_; my $suffix=" SUFFIX"; $_=$prefix . $middle . $suffix;' *MOD
'MOV12A.MOD' would be renamed to 'PREFIX MOV12A.MOD SUFFIX'
Only a real programmer would store his movies with hex names - kudos to you!

Related

rename files keeping basename adding extra word and changing extension

I have a script to encode video files. When I run this script, I would like to keep the basename of the file, add a 'modified' string to the filename and also changing the extension.
The following for loop does that with the exception of changing the extension:
for file in *.mkv;
do
encode $file "${file%%.*}_modified.${i#*.}";
done
I'd like that my_file.mkv will become my_file_modified.mp4. The previous loop just converts my_file.mkv into my_file_modified.mkv
How to change also the extension from .mkv to .mp4?
Thanks in advance.
I'm not totally sure if I got your question right, and probably not; but if I did, you should just do this:
for file in *.mkv;
do
encode $file "${file%.*}_modified.mp4";
done
The ${i#*.} part in your previous command actually took the original extension from the file name; you can just omit it and set your own extension instead.
Also, as #M.NejatAydin pointed out in the comment, you should use ${file%.*} instead of ${file%%.*}, to keep the entire original filename if it has a dot inside it.
For example:
$ file="test.file.mkv"
$ echo "${file%%.*}_modified.mp4"
test_modified.mp4 # This is probably NOT what you want
$ echo "${file%.*}_modified.mp4"
test.file_modified.mp4 # This is probably what you want

How to batch replace part of filenames with the name of their parent directory in a Bash script?

All of my file names follow this pattern:
abc_001.jpg
def_002.jpg
ghi_003.jpg
I want to replace the characters before the numbers and the underscore (not necessarily letters) with the name of the directory in which those files are located. Let's say this directory is called 'Pictures'. So, it would be:
Pictures_001.jpg
Pictures_002.jpg
Pictures_003.jpg
Normally, the way this website works, is that you show what you have done, what problem you have, and we give you a hint on how to solve it. You didn't show us anything, so I will give you a starting point, but not the complete solution.
You need to know what to replace: you have given the examples abc_001 and def_002, are you sure that the length of the "to-be-replaced" part always is equal to 3? In that case, you might use the cut basic command for deleting this. In other ways, you might use the position of the '_' character or you might use grep -o for this matter, like in this simple example:
ls -ltra | grep -o "_[0-9][0-9][0-9].jpg"
As far as the current directory is concerned, you might find this, using the environment variable $PWD (in case Pictures is the deepest subdirectory, you might use cut, using '/' as a separator and take the last found entry).
You can see the current directory with pwd, but alse with echo "${PWD}".
With ${x#something} you can delete something from the beginning of the variable. something can have wildcards, in which case # deletes the smallest, and ## the largest match.
First try the next command for understanding above explanation:
echo "The last part of the current directory `pwd` is ${PWD##*/}"
The same construction can be used for cutting the filename, so you can do
for f in *_*.jpg; do
mv "$f" "${PWD##*/}_${f#*_}"
done

Replace/sync only certain lines using Bash, SSH and rsync

I am looking for a quick and dirty one-liner to sync only certain settings in remote config files. Need to preserve what's unique and sync generic settings. Example:
Config1.conf:
HOSTNAME=COMP1
IP=10.10.13.10
LOCATION=SITE_A
BUILDING=DEPT_IT
ROOM=COMP_LAB1
Remote-Config2.txt:
HOSTNAME=COMP2
IP=10.10.13.11
LOCATION=FOO
BUILDING=BAR
ROOM=BAZ
I need to sync or copy replace only the bottom 3 lines over ssh. The line numbers are predictable, by the way. Always lines 4,5 and 6 in this case.
Here's a working idea that is missing one piece (a standard replacement for the non-standard utility I used to replace the vars in the local conf):
for var in $(ssh root#10.10.8.12 'sed -n "4,6p" /etc/conf1.conf');do <missing piece> ${var/=*}=${var/*=} local-conf.conf; done
So this uses variable expansion and a non-standard utility but needs like a sed or Perl routine to replace the info in the local conf.
Update
The last line of code actually works. Tested and works! However -- the missing piece is a custom non-standard utility. I'm asking if someone can think of something, using standard Linux tools, to replace that.
One solution would be to take the left side and match, then replace the right side. This is basically what that utility does. Looks for the variable in the conf then sets it. Using variable expansion is one way (shown).
Here's an alternative solution that does not require the command to have special knowledge of the file contents:
Take a copy of the files you want to sync. Then, in the copy, deliberately vandalise (arbitrarily modify) the lines you do not want synced. It doesn't matter what they say as long as there are the same number of lines and they'll never match the actual file contents. Have some fun. This becomes your base version. Your example might look like this:
HOSTNAME=foo
IP=bar
LOCATION=SITE_A
BUILDING=DEPT_IT
ROOM=COMP_LAB1
rsync the remote files into a temporary location. This is the remote version.
For each file, take a three-way diff.
diff3 -3 <localfile> <basefile> <remotefile>
The output of diff3 is an "ed script" that decribes what edits to make to the local file so that it would look like the remote file.
The -3 option tells it to only output the non-conflicting differences. This is why we vandalised the base files in the first place: so those lines would have conflicts.
Once you have the ed script for a file, you can visually check it, if you choose, and then apply the update using patch:
cat <ed-script> | patch --ed <localfile>
So, to do this recursively, you might have:
cd $localdir
for file in `find . -type f`; do
diff3 -3 "$file" "$basedir/$file" "$remotedir/$file" | patch --ed "$file"
done
You probably need to add some checks that the base and remote files actually exist.

Issue saving result of "find" function in a shell script

I'm pretty new to shell scripting, but it's been great in helping me automating cumbersome tasks in OS X.
One of the functions I'm trying to write in a new script needs to find the specific filename in a subdirectory given a regex string. While I do know that the file exists, the version (and therefore filename itself) is being continually updated.
My function is currently as follows:
fname(){
$2=$(find ./Folder1 -name "$1*NEW*")
}
Which I'm then calling later in my script with the following line:
fname Type1 filename1
What I'm hoping to do is save the filename I'm looking for in variable filename1. My find syntax seems to be correct if I run it in Terminal, but I get the following error when I run my script:
./myscript.sh: line 13: filename1=./Folder1/Type1-list-NEW.bin: No such file or directory
I'm not sure why the result of find is not just saving to the variable I've selected. I'd appreciate any help (regardless of trivial this question may end up being). Thanks!
EDIT: I have a bunch of files in the subdirectory, but with the way I'm setting that folder up I know my "find" query will return exactly 1 filename. I just need the specific filename to do various tasks (updating, version checking, etc.)
The syntax for writing output to a file is command > filename. So it should be:
fname() {
find ./Folder1 -name "$1*NEW*" > "$2"
}
= is for assigning to a variable, not saving output in a file.
Are you sure you need to put the output in a file? Why not put it in a variable:
fname() {
find ./Folder1 -name "$1*NEW*"
}
var=$(fname Type1)
If you really want the function to take the variable name as a parameter, you have to use eval
fname() {
eval "$2='$(find ./Folder1 -name "$1*NEW*")'"
}
Okay, so I'm reading this as, you want to take the output of the find and save it in a shell variable given by $2.
You can't actually use a shell variable to dynamically declare the name of a new shell variable to rename, when the shell sees an expansion at the beginning of a line it immediately begins processing the words as arguments and not as an assignment.
There might be some way of pulling this off with declare and export but generally speaking you don't want to use a shell variable to hold n file names, particularly if you're on OS X, those file names probably have whitespaces and you're not protecting for that in the way your find is outputting.
Generally what you do in this case is you take the list of files find is spitting out and you act on them immediately, either with find -exec or as a part of a find . -print0 | xargs -0 pipeline.

Bash scripting print list of files

Its my first time to use BASH scripting and been looking to some tutorials but cant figure out some codes. I just want to list all the files in a folder, but i cant do it.
Heres my code so far.
#!/bin/bash
# My first script
echo "Printing files..."
FILES="/Bash/sample/*"
for f in $FILES
do
echo "this is $f"
done
and here is my output..
Printing files...
this is /Bash/sample/*
What is wrong with my code?
You misunderstood what bash means by the word "in". The statement for f in $FILES simply iterates over (space-delimited) words in the string $FILES, whose value is "/Bash/sample" (one word). You seemingly want the files that are "in" the named directory, a spatial metaphor that bash's syntax doesn't assume, so you would have to explicitly tell it to list the files.
for f in `ls $FILES` # illustrates the problem - but don't actually do this (see below)
...
might do it. This converts the output of the ls command into a string, "in" which there will be one word per file.
NB: this example is to help understand what "in" means but is not a good general solution. It will run into trouble as soon as one of the files has a space in its nameā€”such files will contribute two or more words to the list, each of which taken alone may not be a valid filename. This highlights (a) that you should always take extra steps to program around the whitespace problem in bash and similar shells, and (b) that you should avoid spaces in your own file and directory names, because you'll come across plenty of otherwise useful third-party scripts and utilities that have not made the effort to comply with (a). Unfortunately, proper compliance can often lead to quite obfuscated syntax in bash.
I think problem in path "/Bash/sample/*".
U need change this location to absolute, for example:
/home/username/Bash/sample/*
Or use relative path, for example:
~/Bash/sample/*
On most systems this is fully equivalent for:
/home/username/Bash/sample/*
Where username is your current username, use whoami to see your current username.
Best place for learning Bash: http://www.tldp.org/LDP/abs/html/index.html
This should work:
echo "Printing files..."
FILES=(/Bash/sample/*) # create an array.
# Works with filenames containing spaces.
# String variable does not work for that case.
for f in "${FILES[#]}" # iterate over the array.
do
echo "this is $f"
done
& you should not parse ls output.
Take a list of your files)
If you want to take list of your files and see them:
ls ###Takes list###
ls -sh ###Takes list + File size###
...
If you want to send list of files to a file to read and check them later:
ls > FileName.Format ###Takes list and sends them to a file###
ls > FileName.Format ###Takes list with file size and sends them to a file###

Resources