Batch renaming using shell script - bash

I have a folder with files named as
input (1).txt
input (2).txt
input (3).txt
...
input (207).txt
How do I rename them to
input_1.in
input_2.in
input_3.in
...
input_207.in
I am trying this
for f in *.txt ; do mv $f `echo $f | sed -e 's/input\ (\(\d*\))\.txt/input_\1.in/'` ; done
But it gives me
mv: target `(100).txt' is not a directory
mv: target `(101).txt' is not a directory
mv: target `(102).txt' is not a directory
...
Where did I go wrong?
I have put in the quotes now, but I get this now
mv: `input (90).txt' and `input (90).txt' are the same file
It is somehow trying to rename the file to the same name. How is that happening?

That is because bash for split the element with space ' ' so you are commanding it to move 'input' to '(1)'.
The way to solve this is to tell bash to split by new line using IFS variable.
Like this:
IFS=$'\n'
Then do your command.
However, I suggest you to use find to do this instead using -exec command.
For example:
find *.txt -exec mv "{}" `echo "{}" | sed -e 's/input\ (\([0-9]*\))\.txt/input_\1.in/'` \;
NOTE: I write this from memory and I did test this so let try and adjust it.
Hope this helps.

You're forgetting to quote your arguments.
... mv "$f" "$(echo "$f" | ... )" ; done

no need to call external commands
#!/bin/bash
shopt -s nullglob
shopt -s extglob
for file in *.txt
do
newfile="${file//[)]/}"
newfile="${file// [(]/_}"
mv "$file" "${newfile%.txt}.in"
done

As you've already fixed, you need to quote the $f argument to mv.
As to your second problem, sed doesn't support \d. You could use [0-9] instead.

for f in *.txt ; do mv "$f" `echo $f | sed -e 's/input\ (\(\d*\))\.txt/input_\1.in/'` ; done

If you have GNU Parallel http://www.gnu.org/software/parallel/ installed you can do this:
seq 1 207 | parallel -q mv 'input ({}).txt' input_{}.in
Watch the intro video for GNU Parallel to learn more:
http://www.youtube.com/watch?v=OpaiGYxkSuQ

Related

How to use bash string formatting to reverse date format?

I have a lot of files that are named as: MM-DD-YYYY.pdf. I want to rename them as YYYY-MM-DD.pdf I’m sure there is some bash magic to do this. What is it?
For files in the current directory:
for name in ./??-??-????.pdf; do
if [[ "$name" =~ (.*)/([0-9]{2})-([0-9]{2})-([0-9]{4})\.pdf ]]; then
echo mv "$name" "${BASH_REMATCH[1]}/${BASH_REMATCH[4]}-${BASH_REMATCH[3]}-${BASH_REMATCH[2]}.pdf"
fi
done
Recursively, in or under the current directory:
find . -type f -name '??-??-????.pdf' -exec bash -c '
for name do
if [[ "$name" =~ (.*)/([0-9]{2})-([0-9]{2})-([0-9]{4})\.pdf ]]; then
echo mv "$name" "${BASH_REMATCH[1]}/${BASH_REMATCH[4]}-${BASH_REMATCH[3]}-${BASH_REMATCH[2]}.pdf"
fi
done' bash {} +
Enabling the globstar shell option in bash lets us do the following (will also, like the above solution, handle all files in or below the current directory):
shopt -s globstar
for name in **/??-??-????.pdf; do
if [[ "$name" =~ (.*)/([0-9]{2})-([0-9]{2})-([0-9]{4})\.pdf ]]; then
echo mv "$name" "${BASH_REMATCH[1]}/${BASH_REMATCH[4]}-${BASH_REMATCH[3]}-${BASH_REMATCH[2]}.pdf"
fi
done
All three of these solutions uses a regular expression to pick out the relevant parts of the filenames, and then rearranges these parts into the new name. The only difference between them is how the list of pathnames is generated.
The code prefixes mv with echo for safety. To actually rename files, remove the echo (but run at least once with echo to see that it does what you want).
A direct approach example from the command line:
$ ls
10-01-2018.pdf 11-01-2018.pdf 12-01-2018.pdf
$ ls [0-9]*-[0-9]*-[0-9]*.pdf|sed -r 'p;s/([0-9]{2})-([0-9]{2})-([0-9]{4})/\3-\1-\2/'|xargs -n2 mv
$ ls
2018-10-01.pdf 2018-11-01.pdf 2018-12-01.pdf
The ls output is piped to sed , then we use the p flag to print the argument without modifications, in other words, the original name of the file, and s to perform and output the conversion.
The ls + sed result is a combined output that consist of a sequence of old_file_name and new_file_name.
Finally we pipe the resulting feed through xargs to get the effective rename of the files.
From xargs man:
-n number Execute command using as many standard input arguments as possible, up to number arguments maximum.
You can use the following command very close to the one of klashxx:
for f in *.pdf; do echo "$f"; mv "$f" "$(echo "$f" | sed 's#\(..\)-\(..\)-\(....\)#\3-\2-\1#')"; done
before:
ls *.pdf
12-01-1998.pdf 12-03-2018.pdf
after:
ls *.pdf
1998-01-12.pdf 2018-03-12.pdf
Also if you have other pdf files that does not respect this format in your folder, what you can do is to select only the files that respect the format: MM-DD-YYYY.pdf to do so use the following command:
for f in `find . -maxdepth 1 -type f -regextype sed -regex './[0-9]\{2\}-[0-9]\{2\}-[0-9]\{4\}.pdf' | xargs -n1 basename`; do echo "$f"; mv "$f" "$(echo "$f" | sed 's#\(..\)-\(..\)-\(....\)#\3-\2-\1#')"; done
Explanations:
find . -maxdepth 1 -type f -regextype sed -regex './[0-9]\{2\}-[0-9]\{2\}-[0-9]\{4\}.pdf this find command will look only for files in the current working directory that respect your syntax and extract their basename (remove the ./ at the beginning, folders and other type of files that would have the same name are not taken into account, other *.pdf files are also ignored.
for each file you do a move and the resulting file name is computed using sed and back reference to the 3 groups for MM,DD and YYYY
For these simple filenames, using a more verbose pattern, you can simplify the body of the loop a bit:
twodigit=[[:digit:]][[:digit:]]
fourdigit="$twodigit$twodigit"
for f in $twodigit-$twodigit-$fourdigit.pdf; do
IFS=- read month day year <<< "${f%.pdf}"
mv "$f" "$year-$month-$day.pdf"
done
This is basically #Kusalananda's answer, but without the verbosity of regular-expression matching.

remove strange char and adding a prefix when renaming on ssh

i have found this command to replace spaces with underscores:
for file in *.jpg; do mv "$file" ${file// /_}; done
but many pics have chars like ' # ñ and want to remove those automatically and also wanted to add a suffix o prefix.
ex.
pic's#nick_0001.jpg
pic's#nick_0003.jpg
pic's#nick_0003.jpg
to
vacations_pics_nick_0001.jpg
pics_nick_0001_vacations.jpg
can you help me?
for file in *.jpg
do
mv "$file" $(sed 's/[^ [:alnum:]]//g;s/ /_/g;s/^/your_prefix/' <<<"$file")
done
Should do the job
both solution works fine.
is there a way to use that command on a single line, like the example i give to use with others comand together:
for file in * do mv "$file" "${file//[^a-z0-9]/_}" done;
??'
Using bash:
for file in *
do
mv "$file" "${file//[^a-z0-9]/_}"
done
Test:
$ touch "pic's#nick_0001.jpg"
$ ls
pic's#nick_0001.jpg
$ for file in *; do mv "$file" "${file//[^a-z0-9]/_}"; done
$ ls
pic_s_nick_0001_jpg

File renaming using regex

I have a bunch of files in a folder. Some of them are of the format:
IMG_YYYYMMDD_junk.ext
I would like to rename such files into
YYYY-MM-DD junk.ext
Example: IMG_20170214_3939233.jpg becomes 2017-02-14 3939233.jpg
So far I was successful in filtering files I need:
find *.jpg *.jpeg *.png | egrep '^IMG_[0-9]{1,8}'
and I know I need to use sed but I am getting no where specifying and referencing match-groups in my regex for further filename transformation. I know I may have to use xarg later on in the pipe but so far I wasn't successful in transforming each file name just to print it out.
Perhaps, sed is not the best option here.
With Perl‘s standalone rename command and bash‘s option nullglob:
shopt -s nullglob
rename -n 's/.*_(....)(..)(..)_([0-9]+.*)/$1-$2-$3 $4/' *.jpg *.jpeg *.png
If everything looks fine remove option -n.
A logic in bash with NO external tools!
You can run the below script from inside the folder containing these images.
#!/bin/bash
for file in *.{jpg,jpeg,png}; do
IFS="_" read -ra fileNameList <<<"$file"
year="${fileNameList[1]:0:4}"
month="${fileNameList[1]:4:2}"
day="${fileNameList[1]:6:2}"
targetFileName="${year}-${month}-${day} ${fileNameList[2]}"
# Remove this line and uncomment the line with 'mv' if things look OK
echo "$file" "$targetFileName"
#mv -v "$file" "$targetFileName"
done
The idea is tot split the file name on _ and store them in array. Then parse the individual digits from the number and form the final name from the combined elements.
With GNU Parallel it looks like this:
find *.jpg *.jpeg *.png |
parallel mv {} '{= s/IMG_(....)(..)(..)_/$1-$2-$3 / =}'
Or:
parallel mv {} '{= s/IMG_(....)(..)(..)_/$1-$2-$3 / =}' ::: *.jpg *.jpeg *.png
I assume you're not really interested in the find command, but in nailing down the sed regex:
find ~ -type f -maxdepth 1 -name IMG*.jpg | sed -e 's/\(IMG_\)\([0-9]\{4\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)_/\2-\3-\4 /g'
Your example:
echo "IMG_20170214_3939233.jpg" | sed -e 's/\(IMG_\)\([0-9]\{4\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)_/\2-\3-\4 /g'
output:
2017-02-14 3939233.jpg
A mostly-bash solution using its regex-matching operator, =~, which supports capture groups that can be accessed via the built-in "${BASH_REMATCH[#]}" array variable:
for file in *; do
[[ $file =~ ^IMG_([0-9]{4})([0-9]{2})([0-9]{2})_(.+\.(jpg|jpeg|png))$ ]] || continue
mv "$file" "${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]} ${BASH_REMATCH[4]}"
done

remove substring from filename

I have files with name of the form "NAME-xxxxxx.tedx" and I want to remove the "-xxxxxx" part. The x are all digits.
The regex "\-[0-9]{1,6}" matches the substring, but I have no idea how to remove it from the filename.
Any idea how I can do that in the shell?
If you have the perl version of the rename command installed, you could try:
rename 's/-[0-9]+//' *.tedx
Demo:
[me#home]$ ls
hello-123.tedx world-23456.tedx
[me#home]$ rename 's/-[0-9]+//' *.tedx
[me#home]$ ls
hello.tedx world.tedx
This command is smart enough to not rename files if it means overwriting an existing file:
[me#home]$ ls
hello-123.tedx world-123.tedx world-23456.tedx
[me#home]$ rename 's/-[0-9]+//' *.tedx
world-23456.tedx not renamed: world.tedx already exists
[me#home]$ ls
hello.tedx world-23456.tedx world.tedx
echo NAME-12345.tedx | sed "s/-[0-9]*//g"
will give NAME.tedx. So you can use a loop and move the files using mv command:
for file in *.tedx; do
newfile=$(echo "$file" | sed "s/-[0-9]*//g")
mv "$file" $newfile
done
If you want to use just the shell
shopt -s extglob
for f in *-+([0-9]]).tedx; do
newname=${f%-*}.tedx # strip off the dash and all following chars
[[ -f $newname ]] || mv "$f" "$newname"
done

Bash command to remove leading zeros from all file names

I have a directory with a bunch of files with names like:
001234.jpg
001235.jpg
004729342.jpg
I want to remove the leading zeros from all file names, so I'd be left with:
1234.jpg
1235.jpg
4729342.jpg
I've been trying different configurations of sed, but I can't find the proper syntax. Is there an easy way to list all files in the directory, pipe it through sed, and either move or copy them to the new file name without the leading zeros?
for FILE in `ls`; do mv $FILE `echo $FILE | sed -e 's:^0*::'`; done
sed by itself is the wrong tool for this: you need to use some shell scripting as well.
Check Rename multiple files with Linux page for some ideas. One of the ideas suggested is to use the rename perl script:
rename 's/^0*//' *.jpg
In Bash, which is likely to be your default login shell, no external commands are necessary.
shopt -s extglob
for i in 0*[^0]; do mv "$i" "${i##*(0)}"; done
Maybe not the most elegant but it will work.
for i in 0*
do
mv "${i}" "`expr "${i}" : '0*\(.*\)'`"
done
Try using sed, e.g.:
sed -e 's:^0*::'
Complete loop:
for f in `ls`; do
mv $f $(echo $f | sed -e 's:^0*::')
done
I dont know sed at all but you can get a listing by using find:
find -type f -name *.jpg
so with the other answer it might look like
find . -type f -name *.jpg | sed -e 's:^0*::'
but i dont know if that sed command holds up or not.
Here's one that doesn't require sed:
for x in *.jpg ; do let num="10#${x%%.jpg}"; mv $x ${num}.jpg ; done
Note that this ONLY works when the filenames are all numbers. You could also remove the leading zeros using the shell:
for a in *.jpg ; do dest=${a/*(0)/} ; mv $a $dest ; done
In Bash shell you can do:
shopt -s nullglob
for file in 0*.jpg
do
echo mv "$file" "${file##*0}"
done

Resources