Renaming multiple files using a Shell Script - shell

I have files named t1.txt, t2.txt, t3.txt ... t4.txt and I need a shell script to rename it like this:
file one: M.m.1.1.1.201108290000.ready
file two: M.m.1.1.1.201108290001.ready
etc, the sequence number in the last 4 digits changes.
I'd be grateful if someone helped me :)
Best Regards

This might be what you need:
cd /home/me/Desktop/files/renam/
n=201108290000
for file in *.txt; do
echo $file
prefix=M.m.1.1.1.
file_name=M.m.1.1.1.$n.ready
echo $file_name
n=$(( $n+1 ))
mv $file $file_name
done
It's close to what you'd written yourself, you just missed some bash syntax. Note that you might want to change the initial value of n, otherwise for the files you mentioned t1.txt would become M.m.1.1.1.201108290000.ready. Depending on what your use is, that might be confusing.
I'd also advice you to avoid use the names of programs and builtins as variable names, such as seq in your case.

Related

Using brace expansion to move files on the command line

I have a question concerning why this doesn't work. Probably, it's a simple answer, but I just can't seem to figure it out.
I want to move a couple of files I have. They all have the same filename (let's say file1) but they are all in different directories (lets say /tmp/dir1,dir2 and dir3). If I were to move these individually I could do something along the lines of:
mv /tmp/dir1/file1 /tmp
That works. However, I have multiple directories and they're all going to end up in the same spot....AND I don't want to overwrite. So, I tried something like this:
mv /tmp/{dir1,dir2,dir3}/file1 /tmp/file1.{a,b,c}
When I try this I get:
/tmp/file1.c is not a directory
Just to clarify...this also works:
mv /tmp/dir1/file1 /tmp/file1.c
Pretty sure this has to do with brace expansion but not certain why.
Thanks
Just do echo to understand how the shell expands:
$ echo mv /tmp/{dir1,dir2,dir3}/file1 /tmp/file1.{a,b,c}
mv /tmp/dir1/file1 /tmp/dir2/file1 /tmp/dir3/file1 /tmp/file1.a /tmp/file1.b /tmp/file1.c
Now you can see that your command is not what you want, because in a mv command, the destination (directory or file) is the last argument.
That's unfortunately now how the shell expansion works.
You'll have to probably use an associative array.
!/bin/bash
declare -A MAP=( [dir1]=a [dir2]=b [dir3]=c )
for ext in "${!MAP[#]}"; do
echo mv "/tmp/$ext/file1" "/tmp/file1.${MAP[$ext]}"
done
You get the following output when you run it:
mv /tmp/dir2/file1 /tmp/file1.b
mv /tmp/dir3/file1 /tmp/file1.c
mv /tmp/dir1/file1 /tmp/file1.a
Like with many other languages key ordering is not guaranteed.
${!MAP[#]} returns an array of all the keys, while ${MAP[#]} returns the an array of all the values.
Your syntax of /tmp/{dir1,dir2,dir3}/file1 expands to /tmp/dir1/file /tmp/dir2/file /tmp/dir3/file. This is similar to the way the * expansion works. The shell does not execute your command with each possible combination, it simply executes the command but expands your one value to as many as are required.
Perhaps instead of a/b/c you could differentiate them with the actual number of the dir they came from?
$: for d in 1 2 3
do echo mv /tmp/dir$d/file1 /tmp/file1.$d
done
mv /tmp/dir1/file1 /tmp/file1.1
mv /tmp/dir2/file1 /tmp/file1.2
mv /tmp/dir3/file1 /tmp/file1.3
When happy with it, take out the echo.
A relevant point - brace expansion is not a wildcard. It has nothing to do with what's on disk. It just creates strings.
So, if you create a bunch of files named with single letters or digits, echo ? will wildcard and list them all, but only the ones actually present. If there are files for vowels but not consonants, only the vowels will show. But -
if you say echo {foo,bar,nope} it will output foo bar nope regardless of whether or not any or all of those exist as files or directories, etc.

Remove part of name of multiple files in Linux

I have several fastq.gz files in a directory. I want to delete parts of each file name.
Here are the file names:
RES_1448_001_S289_L001_R1_001.fastq.gz
RES_1448_001_S289_L001_R2_001.fastq.gz
RES_1448_012_S300_L001_R1_001.fastq.gz
RES_1448_012_S300_L001_R2_001.fastq.gz
I want to remove S and 3 digits after it. I expect this after removing
RES_1448_001_R1_001.fastq.gz
RES_1448_001_R2_001.fastq.gz
RES_1448_012_R1_001.fastq.gz
RES_1448_012_R2_001.fastq.gz
I asked a similar question before, but was advised to ask a new one to cover the precise requirements I have now.
Old question: Delete part of name of multiple files in Linux
Use rename.
rename 's/S\d{3}_//' *.fastq.gz
Using this bash, regEx would do the trick for you.
#!/bin/bash
for file in *.fastq.gz
do
if [[ $file =~ ^(.*)S([[:digit:]]{3})_L([[:digit:]]{3})_(.*)$ ]]
then
start="${BASH_REMATCH[1]}"
end="${BASH_REMATCH[4]}"
mv -- "$file" "${start}${end}"
fi
done

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.

Batch editing files 'stuck at weird place'

I'm trying to learn how to batch edit files and extract information from them. I've begun with trying to create some trial files and editing their names. I tried to search but couldn't find the problem I'm in anywhere.
If it's already answered, I'd be happy to be directed to that link.
So, I wrote the following code:
#!/bin/bash
mkdir -p ./trialscript
echo $1
i=1
while [ $i -le $1 ]
do
touch ./trialscript/testfile$i.dat
i=$(($i+1))
done
for f in ./trialscript/*.dat
do
echo $f
mv "$f" "$fhello.dat"
done
This doesn't seem to work, and I think it's because the echo output is like:
4
./trialscript/testfile1.dat
./trialscript/testfile2.dat
./trialscript/testfile3.dat
./trialscript/testfile4.dat
I just need the filename in the 'f' and not the complete path and then just rename it.
Can someone suggest what is wrong in my code, and what's correct way to do what I'm doing.
If you want to move the file, you have to use the path, too, otherwise mv wouldn't be able to find it.
The target specification for the mv command is more problematic, though. You're using
"$fhello.dat"
which, in fact, means "content of the $fhello variable plus the string .dat". How should the poor shell know where the seam is? Use
"${f}hello.dat"
to disambiguate.
Also, to extract parts of strings, see Parameter expansion in man bash. You can use ${f%/*} to only get the path, or ${f##*/} to only get the filename.

Properly handle lists of files with whitespace in filename

I want to iterate over a list of files in Bash and perform some action. The problem: the file names may contain whitespace, which creates an obvious problem with wildcards or ls:
touch a\ b
FILES=* # or $(ls)
for FILE in $FILES; do echo $FILE; done
yields
a
b
Now, the conventional way to handle this is to use find … -print0 instead. However, this only works (well) in conjunction with xargs -0, not with Bash variables / loops.
My idea was to set $IFS to the null character to make this work. However, the comp.unix.shell seems to think that this is impossible in bash.
Bummer. Well, it’s theoretically possible to use another character, such as : (after all, $PATH uses this format, too):
IFS=$':'
FILES=$(find . -print0 | xargs -0 printf "%s:")
for FILE in $FILES; do echo $FILE; done
(The output is slightly different but fair enough.)
However, I can’t help but feel that this is clumsy and that there should be a more direct way of doing this. I’m looking for a more direct way of accomplishing this, preferably using wildcards or ls.
The best way to handle this is to store the file list as an array, rather than a string (and be sure to double-quote all variable substitutions):
files=(*)
for file in "${files[#]}"; do
echo "$file"
done
If you want to generate an array from find's output (e.g. if you need to search recursively), see this previous answer.
Exactly what you have in the first example works fine for me in Msys Bash, Cygwin and on my Fedora box:
FILES=*
for FILE in $FILES
do
echo $FILE
done
Its very important to preceed
IFS=""
otherwise files with two directly following spaces will not be found

Resources