Way to move files in bash and rename copied file automatically without overwriting an existing file - macos

I'm doing some major restructuring of large numbers of directories with tons of jpgs, some of which my have the same name as files in other directories. I want to move / copy files to alternate directories and have bash automatically rename them if the name matches another file in that directory (renaming IMG_238.jpg to IMG_238_COPY1.jpg, IMG_238_COPY2.jpg, etc), instead of overwriting the existing file.
I've set up a script that takes jpegs and moves them to a new directory based on exif data. The final line of the script that moves one jpg is: mv -n "$JPEGFILE" "$DIRNAME"
I'm using the -n option because I don't want to overwrite files, but now I have to go and manually sort through the ones that didn't get moved / copied. My GUI does this automatically... Is there a relatively simple way to do this in bash?
(In case it matters, I'm using bash 3.2 in Mac OSX Lion).

This ought to do it
# strip path, if any
fname="${JPEGFILE##*/}"
[ -f "$DIRNAME/$fname" ] && {
n=1
while [ -f "$DIRNAME/${fname%.*}_COPY${n}.${fname##*.}" ] ; do
let n+=1
done
mv "$JPEGFILE" "$DIRNAME/${fname%.*}_COPY${n}.${fname##*.}"
} || mv "$JPEGFILE" "$DIRNAME"
EDIT: Improved.

You can try downloading and seeing if Ubuntu/Debian's Perl-based rename works. It has sed-style functionality. Quoth the man page (on my system, but the script should be the same one as linked):
"rename" renames the filenames supplied according to the rule specified
as the first argument. The perlexpr argument is a Perl expression
which is expected to modify the $_ string in Perl for at least some of
the filenames specified. If a given filename is not modified by the
expression, it will not be renamed. If no filenames are given on the
command line, filenames will be read via standard input.
For example, to rename all files matching "*.bak" to strip the
extension, you might say
rename 's/\.bak$//' *.bak
To translate uppercase names to lower, you'd use
rename 'y/A-Z/a-z/' *

Related

Organizing Files In Directories with Terminal

So I am wondering if there is any way to organize a directory on a mac with the terminal. I am a beginner with using the terminal and just seeing if this is possible.
I have a script that will scrape various pages and save certain data to a file (data irrelevant), such as this picture.
directory that needs organizing
I would like to know if I can write something that will read the file names and create directories that correspond. For example, it runs a loop that will read all files with "Year2014", create a folder named "Year2014", then place the files inside.
If you have any other questions, let me know!
The short answer is "Yes", and the longer answer is there are many ways to do it. Since you are using bash (or any POSIX shell), you have parameter expansion with substring removal available to help you trim text from the end of each filename to isolate the "YearXXXX" part of the filename that you can then use to (1) create the directory, and (2) move the file into the newly created directory.
Presuming Filenames Formatted WeekXXYearXXXX.txt
Take for example a simple for loop where the loop variable f will contain each filename in turn. You can isolate the "WeekXX" part of the name by using a parameter expansion that trims from the right of the string trough 'Y' leaving whatever "WeekXX" is. (save the result in a temporary variable) You can then use that temp variable to remove the "WeekXX" text from the original filename leaving "YearXXXX.txt". You then simply remove ".txt" from the first to arrive at the directory name to put the file in.
Scriptwise it would look like:
for f in *.txt; do ## loop over .txt files using variable $f
tmp="${f%%Y*}" ## remove though 'Y' from right
dname="${f#$tmp}" ## remove contents of tmp from left
dname="${dname%.txt}" ## remove .txt
mkdir -p "$dname" ## create dname (no error if exists)
mv "$f" "$dname" ## move $f to $dname
done
Where the temporary variable used is tmp and the final directory name is stored in the variable dname.
(note: you may want to use mv -i if you want mv to prompt before overwriting if the filename already exists in the target directory)
You can refer to man bash under the Parameter Expansion heading to read the specifics of each expansion which (among many more) are described as:
${var#pattern} Strip shortest match of pattern from front of $var
${var##pattern} Strip longest match of pattern from front of $var
${var%pattern} Strip shortest match of pattern from back of $var
${var%%pattern} Strip longest match of pattern from back of $var
Note this set of parameter expansions is POSIX so it will work with any POSIX shell, while most of the remaining expansions are bashisms (bash-only)
Let me know if you have further questions.

bash: using rename to left pad filenames with a zero under when their prefix is too short

I'm using a naming convention with number prefixes to track some files. But I am running out with 2-digit prefix. So, instead of 11.abc 12.def I want to move to 011.abc 012.def. I already have some 013.xxx 014.yyy.
Trying this in an empty directory:
touch 11.abc 12.def 013.xxx 014.yyy
ls -1 gives:
013.xxx
014.yyy
11.abc
12.def
Try #1:
This should match anything that starts with 2 digits, but not 3.
rename -n 's/^\d\d[^\d]/0$1/' *
Now I was kind of hoping that $1 would hold the match, like 11, with 0$1 giving me 011.
No such luck:
Use of uninitialized value $1 in concatenation (.) or string at (eval 2) line 1.
'11.abc' would be renamed to '0abc'
Use of uninitialized value $1 in concatenation (.) or string at (eval 2) line 1.
'12.def' would be renamed to '0def'
On the positive side, it's willing to leave 013 and 014 alone.
Try #2 rename -n 's/^\d\d[^\d]/0/' *
'11.abc' would be renamed to '0abc'
'12.def' would be renamed to '0def'
Since this is regex based, can I somehow save the match group 11 and 12?
If I can't use rename I'll probably write a quick Python script. Don't want to loop with mv on it.
And, actually, my naming covention is 2-3 digits followed by a dot, so this is a good match too.
rename -n 's/^\d\d\./<whatever needs to go here>/' *
For what it's worth, I am using the Homebrew version of rename, as I am on a mac.
try this:
rename 's/^(\d{2}\..*)/0$1/' *
rename is problematic because it's not part of POSIX (so it isn't normally available on many Unix-like systems), and there are two very different forms of it in widespread use. See Why is the rename utility on Debian/Ubuntu different than the one on other distributions, like CentOS? for more information.
This Bash code does the renaming with mv (which is part of POSIX):
#! /bin/bash -p
shopt -s nullglob # Patterns that match nothing expand to nothing.
for f in [0-9][0-9].* ; do
mv "$f" "0$f"
done
shopt -s nullglob is to prevent problems if the code is run in a directory that has no files that need to be renamed. If nullglob isn't enabled the code would try to rename a file called '[0-9][0-9].*', which would have unwanted consequences whether or not such a file existed.

In Bash, how do I take pairs of files and put them into directories with matching names?

I have a bunch of files like this (currently all in one directory, but I can separate them by file type or whatever if need be):
Pep_1-1.pdb
Pep_1-1.psf
Pep_1-2.pdb
Pep_1-2.psf
Pep_1-3.pdb
...
I want to take each pair, make a directory with the corresponding name and then place the two files in that directory (steps don't have to be in this order, I just care about the outcome), so that I have directories like Pep_1-1, Pep_1-2, etc. each containing the two corresponding files. What's the most efficient way to do that?
Thanks :)
Assuming the files always exist in pairs, it's easiest to iterate over one of the pair and extract the name sans extension.
for f in *.pdb; do
basename=${f%.*}
mkdir "$basename"
mv "$f" "$basename.psf" "$basename"
done
You could use sed and awk or use basename but I think simple problems should be met with simple solutions. This is why I asked if your files will always be in the form of Pep_1-#.pdb and Pep_1-#.psf.
Simply build the for loop as follows:
for i in `seq 1 50`;
do
mkdir "Pep_1-$i";
# Cannot do glob expansion
cp "Pep_1-$i.pdb" "Pep_1-$i/";
cp "Pep_1-$i.psf" "Pep_1-$i/";
done
Always backup your directories before testing!

How to batch rename a file of specific file type within subdir?

I'd like to batch rename all files within a specific subdirectory. Why does the following give a syntax error?
RENAME .\webapps\*.war .\webapps\Test.war
You can't rename with a target name that includes a path.
RENAME .\webapps\*.war Test.war
should work.
There is some confusion in your statement: Do you want to change all the files to the same name? It's impossible.
And you might use rename the wrong way, please refer to man rename.
NAME
rename - renames multiple files
SYNOPSIS
rename [ -v ] [ -n ] [ -f ] perlexpr [ files ]
DESCRIPTION
"rename" renames the filenames supplied according to the rule specified as the first argument. The perlexpr argument is a Perl expression which is
expected to modify the $_ string in Perl for at least some of the filenames specified. If a given filename is not modified by the expression, it will not
be renamed. If no filenames are given on the command line, filenames will be read via standard input.
For example, to rename all files matching "*.bak" to strip the extension, you might say
rename 's/\.bak$//' *.bak
To translate uppercase names to lower, you'd use
rename 'y/A-Z/a-z/' *
OPTIONS
-v, --verbose
Verbose: print names of files successfully renamed.
-n, --no-act
No Action: show what files would have been renamed.
-f, --force
Force: overwrite existing files.
This post might also be useful.

Rename files based on symbols in the name

I have a lot of files (images) like this (file names consist only from numbers):
123456.jpg
369258.jpg
987123.jpg
...
I need to make a copy of each in some other folder (let's name it output) and rename each of the file based on numbers in their name, something like this (in pseudocode):
outputFileName = String(filename[0]) + String(filename[1]) + String(filename[2]+filename[3]) + ".jpg"
So as you can see, the renaming involves getting a certain symbol in file name and sometimes getting a sum of some symbols in file name.
I need to make a script to mass rename all *.jpg in the folder where I put the script based on similar algorithm, and output renamed ones in output folder I mentioned earlier.
This script should be workable from macos terminal and windows via cygwin shell.
I assume main problems are: how to get particular character of bash variable and how to perform addition in bash.
To obtain a char from bash variable you can use this form: ${var:START_INDEX:LENGTH}.
To perform addition: $((ARG1 + ARG2))
Your resulting script may be like that:
#!/bin/bash
for f in *.jpg
do
output=${f:0:1}${f:1:1}$((${f:2:1} + ${f:3:1})).jpg
mv -- "$f" "$output"
done
You are looking for substring extraction.
The syntax is ${string:position:length}, where string is the name of the variable, position is the starting position (0 is the first index), and length is the length of the substring.
A script that would create the filenames as specified in the question, and copy them for a folder named "input" to a folder named "output" could look like this:
#!/bin/bash
for file in input/*.jpg
do
filename="$(basename "$file")"
firstChar="${filename:0:1}"
secondChar="${filename:1:1}"
thirdAndFourthChar="$(( ${filename:2:1} + ${filename:3:1} ))"
newfilename="$firstChar$secondChar$thirdAndFourthChar.jpg"
cp "$file" "output/$newfilename"
done

Resources