LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Rename files matching a list (https://www.linuxquestions.org/questions/programming-9/rename-files-matching-a-list-739558/)

Qu3ry 07-12-2009 11:44 AM

Rename files matching a list
 
Is there a way, preferably in python or BASH, to rename files from a list? for instance, track1.mp3, track2.mp3 should be renamed to the names stored in a file listing song names.

I have tried to loop a variable through directory listing and renamed them, only to find that filenames with spaces can't be assigned to a variable as a whole.

To solve the problem above, I have tried the read command in BASH, which enables the program reading line by line from a list. However, It was failed to pipe the results from directory listing to the read command.

Any help is appreciated !

catkin 07-12-2009 12:22 PM

Hello Qu3ry :)

Yes, it's possible. You've probably got some bash quoting issues. If you post your script we can take it from there.

Best

Charles

sutrannu 07-12-2009 02:28 PM

Code:

  for x in `seq 1 $(ls *.mp3|wc -l)`; do mv `ls *.mp3|head -$x|tail -1` `cat Track_List.txt|head -$x|tail -1|sed 's/[^0-9a-zA-Z]/_/g'`.mp3; done
Something like this would rename the mp3s in a folder with names from Track_List.txt if the number of mp3s matched the names in the file, they were in the same order, etc.
This is quick & dirty. Backup, and test first.
This will replace all non-alphanumeric characters with "_".

Qu3ry 07-14-2009 08:57 AM

How to combine these two?
 
Condition A
Code:

#!/bin/bash

for i in `ls *.mp3`; do echo i is $i; done

Condition B
Code:

#!/bin/bash

cat test.txt | while read j; do echo j is $j; done

The idea is to rename $i to $j. How to combine the first two conditions using the AND logical operator?

ie. If condition A and B is true, then rename i into j for each iteration

catkin 07-14-2009 10:19 AM

Hello Qu3ry :)

A and B are loops, not "conditions". In programming, a condition is a test -- equality, greater than, less than , existence etc.

How about reading the list of files into an array first and then loop through the lines of test.txt?

Code:

files=($(ls *.mp3))
i=0
cat test.txt | while read j
do
    echo mv "${files[ $i ]}" "$j"
    $(( i = i + 1 ))
done

The var=(<stuff>) creates var as an array and assigns the (whitespace separated) words of <stuff> to successive elements of the array. By default, whitespace is space, tab and newline . The form given above will work as long as there is no whitespace in the *.mp3 file names.

$(<stuff>) is doing the same as your `<stuff>` but is more robust.

$i is set to 0 before starting the loop because that is the index of the first element of the array.

Putting the $j in double quotes overcomes the problem of file names including spaces; it keeps the whole name together as a single word; without the double quotes, each (whitespace separated) word of $j would become a separate argument to the echo (later mv) command.

$(( <stuff> )) is for doing arithmetic.

The code is proof of concept (and not tested!) rather than smart. It doesn't check for test.txt having the same number of lines as there are *.mp3 files and it would be easier to read if $j was renamed to something meaningful like $track_name.

Best

Charles

Qu3ry 07-15-2009 11:38 AM

Thanks catkin for your help.

Some song names listed in the file ends with a trailing ^M, probably as a result of Windows-copy-and-paste thingy. I ran the script before using the dos2unix command. Songs are bearing weird endings. For instance, songname??.mp3

I have tried to rename them by using the rename command, but in vain.

Code:

#rename ^M.mp3 .mp3 *
Having tried \^M.mp3, "\^M.mp3" or ?.mp3, I found nothing work so far.

Qu3ry 07-15-2009 11:51 AM

BTW, the following msg was found in the prompt when running your script. ( I have the echo command removed btw)

Quote:

/usr/bin/match.sh: line 8: 1: command not found
/usr/bin/match.sh: line 8: 2: command not found
/usr/bin/match.sh: line 8: 3: command not found
/usr/bin/match.sh: line 8: 4: command not found
/usr/bin/match.sh: line 8: 5: command not found
/usr/bin/match.sh: line 8: 6: command not found
/usr/bin/match.sh: line 8: 7: command not found
/usr/bin/match.sh: line 8: 8: command not found
/usr/bin/match.sh: line 8: 9: command not found
/usr/bin/match.sh: line 8: 10: command not found
/usr/bin/match.sh: line 8: 11: command not found
/usr/bin/match.sh: line 8: 12: command not found
/usr/bin/match.sh: line 8: 13: command not found
/usr/bin/match.sh: line 8: 14: command not found
/usr/bin/match.sh: line 8: 15: command not found
/usr/bin/match.sh: line 8: 16: command not found
/usr/bin/match.sh: line 8: 17: command not found
/usr/bin/match.sh: line 8: 18: command not found
/usr/bin/match.sh: line 8: 19: command not found
/usr/bin/match.sh: line 8: 20: command not found
/usr/bin/match.sh: line 8: 21: command not found
/usr/bin/match.sh: line 8: 22: command not found
/usr/bin/match.sh: line 8: 23: command not found
/usr/bin/match.sh: line 8: 24: command not found

catkin 07-15-2009 12:20 PM

Hello Qu3ry :)

The Ctrl+M characters can be removed in the script:
Code:

ctlM=$'\r'
files=($(ls *.mp3))
i=0
cat test.txt | while read j
do
    echo mv "${files[ $i ]}" "${j%$ctlM}"
    $(( i = i + 1 ))
done

The form var=$'<stuff>' allows C escape sequences to be used in defining a string. C escape sequence \r is Ctrl+M.

Any text editor can be used to create the above script. If you are using an editor that allows insertion of Ctrl+M you wouldn't need the $ctlM variable. In vi you can enter it directly by pressing Ctrl+V then Ctrl+M. So you could replace "${j%$ctlM}" with "${j%$^M}".

Best

Charles

Qu3ry 07-15-2009 02:18 PM

This time is little bit different. I was about to rename a bunch of mp3 files having the following patterns:

1-01 track.mp3
1-02 track.mp3
1-03 track.mp3
1-04 track.mp3
1-05 track.mp3
1-06 track.mp3

2-01 song.mp3
2-02 song.mp3
2-03 song.mp3
2-04 song.mp3

How to merge all split files according to their track numbers? There is no info on how many parts are split for each song. The only info we know is they all have the same number prefixes.

The problem with for loop is that you have to know the largest number prefix in orker to loop. Also, is there a better way to extract their filenames?

Quote:

i=1
for i < 30
do
cat "$i"* > "$i".mp3
i = i + 1
done

Qu3ry 07-15-2009 02:30 PM

manipulating song lists
 
Problem with the tr command in BASH

I want to replace all filenames beginning with single digit prefixes into muliti-digits. For instance 1.song.mp3 into 01.song.mp3.

Code:

tr '^[1-9]\.$' '0[1-9]\.' <text.txt
It didn't work for unknown reasons.

Another interesting thing is that when text.txt is streamed through tr with the same filename as output, it didn't replace the old one with the new.

Code:

tr 'a' '1' <test.txt > text.txt
text.txt would be blank. why ? I have to use a new filename other than text.txt as output.

catkin 07-15-2009 02:39 PM

Quote:

Originally Posted by Qu3ry (Post 3608378)
BTW, the following msg was found in the prompt when running your script. ( I have the echo command removed btw)

Did you solve that issue? If not, try replacing
Code:

$(( i = i + 1 ))
with
Code:

let i=i+1

catkin 07-15-2009 02:41 PM

Quote:

Originally Posted by Qu3ry (Post 3608581)
This time is little bit different. I was about to rename a bunch of mp3 files having the following patterns:

1-01 track.mp3
1-02 track.mp3
1-03 track.mp3
1-04 track.mp3
1-05 track.mp3
1-06 track.mp3

2-01 song.mp3
2-02 song.mp3
2-03 song.mp3
2-04 song.mp3

How to merge all split files according to their track numbers? There is no info on how many parts are split for each song. The only info we know is they all have the same number prefixes.

What does the corresponding test.txt file look like?

Qu3ry 07-15-2009 02:53 PM

Quote:

Originally Posted by catkin (Post 3608612)
What does the corresponding test.txt file look like?

1.new year.mp3
2.linux.mp3
3.hello.mp3

etc...

Qu3ry 07-15-2009 03:10 PM

Quote:

Originally Posted by catkin (Post 3608609)
Did you solve that issue? If not, try replacing
Code:

$(( i = i + 1 ))
with
Code:

let i=i+1

after replacing it with "let i=i+1"

/usr/bin/match.sh: line 9: let: =: syntax error: operand expected (error token is "=")

catkin 07-15-2009 03:11 PM

Quote:

Originally Posted by Qu3ry (Post 3608594)
Problem with the tr command in BASH

I want to replace all filenames beginning with single digit prefixes into muliti-digits. For instance 1.song.mp3 into 01.song.mp3.

Code:

tr '^[1-9]\.$' '0[1-9]\.' <text.txt

I don't understand what the tr operands (SET1 and SET2 on the man page) are and anyway, what's going to happen when you get to 10.song.mp3? Might be easier to parse out the number (by ${name%%.*}) and format it as you want using printf.

Quote:

Originally Posted by Qu3ry (Post 3608594)
Code:

tr 'a' '1' <test.txt > text.txt
text.txt would be blank. why ? I have to use a new filename other than text.txt as output.

The shell empties the file after the > before running the command.

catkin 07-15-2009 03:15 PM

Quote:

Originally Posted by Qu3ry (Post 3608626)
1.new year.mp3
2.linux.mp3
3.hello.mp3

etc...

Sorry, I'm still not clear exactly what you want to do here. You want to rename which *.mp3 files? The "1-01 track.mp3" etc ones as "new year 01 track.mp3" etc?

Qu3ry 07-15-2009 03:53 PM

Quote:

Originally Posted by catkin (Post 3608658)
Sorry, I'm still not clear exactly what you want to do here. You want to rename which *.mp3 files? The "1-01 track.mp3" etc ones as "new year 01 track.mp3" etc?

1-01 track.mp3 is one of the split files of 1.new year.mp3.

first to merge, then to rename them.

catkin 07-15-2009 04:25 PM

Quote:

Originally Posted by Qu3ry (Post 3608715)
1-01 track.mp3 is one of the split files of 1.new year.mp3.

first to merge, then to rename them.

How do you merge them?

Qu3ry 07-16-2009 04:02 PM

cat 01-*.mp3 > 01.mp3
cat 02-*.mp3 > 02.mp3

etc..

Writing a for loop to automate the task. The problem is that you have to know number of tracks to be merged in each directory.



Code:


i = 01
j = 32 # number of tracks to be merged

for i <= j ; do

  cat "$i"-*.mp3 > "$i".mp3;
  i = $(i + 1)

done


ntubski 07-16-2009 08:50 PM

What language are those for loops in? I'm pretty that is not valid bash.

This should work, assuming the song number is always separated by a "-" and is the first thing in the file name. Also it might screw up if there are other files in the directory.
Code:

for song in $(ls *-*.mp3 | cut -d- -f1 | sort -u) ; do
  cat "$song"-*.mp3 > "$song".mp3
done


Qu3ry 07-17-2009 01:55 PM

ntubski, your script should work. However, I found a new pattern in other directories.. Some songs aren't grouped together by their number prefixes. More precisely they are grouped by their names.. for instance..

01-1.song.mp3
01-2.song.mp3
01-3.pop.mp3
01-4.pop.mp3

There are two songs in those 4 files. How to change the script accordingly?

catkin 07-17-2009 03:23 PM

Hello Qu3ry :)
Quote:

Originally Posted by Qu3ry (Post 3609953)
cat 01-*.mp3 > 01.mp3
Code:


i = 01
j = 32 # number of tracks to be merged

for i <= j ; do

  cat "$i"-*.mp3 > "$i".mp3;
  i = $(i + 1)

done


Bash requires no spaces either side of the = command in an assignment. Thus i = 01 does not assign 01 to i. Instead it tries to run the command i with operands = and 01.

Bash interprets integer constants beginning with 0 as octal. Doesn't make any difference for 01 to 07 but 09 would be invalid.

The syntax of the "for" statement is either
Code:

for name [in words ...]
or
Code:

for (( expr1 ; expr2 ; expr3 ))
It looks as if you wanted
Code:

while test-commands
The <= comparison operator is for strings unless you are doing shell arithmetic in a let <arithmetic expression>, ((<arithmetic expression>)) or $((<arithmetic expression>)). The bash test numeric comparison operator is -le.

In cat "$i"-*.mp3 > "$i".mp3; the ";" is OK but not necessary because the line end serves the same purpose.

i = $(i + 1) will not do what you want, first because of the spaces either side of the "=" and second because $(i + 1) will try to run command i with operands + and 1. i=((i + 1)) would do what you want because bash evaluates expressions within (( )) as arithmetic and substitutes their value. Assuming $i is 5 then i=((i + 1)) becomes i=6

This works:
Code:

for (( i=1; i<=32; i++ ))
do
    cat "$i"-*.mp3 > "$i".mp3
done

BTW, I'm amazed and educated to learn that you can cat *.mp3 files together and play the result. Please confirm you have tested this.

Best

Charles

ntubski 07-17-2009 03:47 PM

Quote:

Originally Posted by Qu3ry (Post 3611030)
01-1.song.mp3
01-2.song.mp3
01-3.pop.mp3
01-4.pop.mp3

There are two songs in those 4 files. How to change the script accordingly?

changes in red.

Code:

for song in $(ls *-*.*.mp3 | cut -d. -f2 | sort -u) ; do
  cat *."$song".mp3 > "$song".mp3
done

Quote:

Originally Posted by catkin
BTW, I'm amazed and educated to learn that you can cat *.mp3 files together and play the result.

You know, now that you mention it, it does seem surprising.

Qu3ry 07-17-2009 04:15 PM

I have been using the cat command to merge avi and mp3 files for awhile with no problem.

For further reference: an article on looping filenames with spaces:

http://www.cyberciti.biz/tips/handli...s-in-bash.html

catkin 07-17-2009 04:51 PM

Hello Qu3ry :)
Quote:

Originally Posted by Qu3ry (Post 3611157)
I have been using the cat command to merge avi and mp3 files for awhile.

For further reference: an article on looping filenames with spaces:

http://www.cyberciti.biz/tips/handli...s-in-bash.html

Thanks for the info.

Slightly scary page on the link though ...

"By default $IFS is set to the space character" is not correct. By default it is set to space, tab and newline.

There's no need to adjust IFS for this code to work when files in the current directory contain spaces (and tabs and newlines!)
Code:

for f in *
do
  echo "$f"
done

It works just fine with the default IFS!

I'm surprised that IFS=$SAVEIFS works. What if there are current IFS characters in $SAVEIFS? Wouldn't the shell evaluate $SAVEIFS into more than one word? IFS="$SAVEIFS" is safer. Mmm ... :confused:

In IFS=$(echo -en "\n\b"), why put the backspace character in IFS? Mmm ... :confused:

while IFS=: read userName passWord userID groupID geCos homeDir userShell is perfectly correct but neither transparent nor efficient; its only merit is to save a line. Functionally this is identical
Code:

IFS=:
while read userName passWord userID groupID geCos homeDir userShell

Best

Charles

Qu3ry 07-18-2009 07:54 AM

Quote:

Originally Posted by ntubski (Post 3611138)
changes in red.

Code:

for song in $(ls *-*.*.mp3 | cut -d. -f2 | sort -u) ; do
  cat *."$song".mp3 > "$song".mp3
done


You know, now that you mention it, it does seem surprising.

ntubski, the new script didn't work. There is no mp3 matches the pattern to begin with. I have changed the code to (ls *-*.mp3) to match the pattern and ran the code with the echo command instead of the cat command. It didn't work neither. Here is the result:

*.mp3.mp3 to mp3.mp3

where "to" is the replacement of ">"

Qu3ry 07-18-2009 08:24 AM

catkin, I find using files=($(ls *.mp3)) as an array assignment not able to deal with filenames with spaces. Changing it to files=( *.mp3 ) can solve the problem.



Code:


files=( *.mp3 )
i=0
cat test.txt | while read j
do
    mv "${files[ $i ]}" "$j".mp3
    let i++
done

BTW, for those of you who are lucky to have id3 tag attached to their mp3es, there is a program to rename mp3es according to their id3 tag and vice versa.

http://pwp.netcabo.pt/paol/tagtool/

catkin 07-18-2009 09:22 AM

Hello Qu3ry :)
Quote:

Originally Posted by Qu3ry (Post 3611730)
catkin, I find using files=($(ls *.mp3)) as an array assignment not able to deal with filenames with spaces. Changing it to files=( *.mp3 ) can solve the problem.

Thanks for the correction and sorry for the mistake.

Best

Charles

ntubski 07-18-2009 12:15 PM

Quote:

Originally Posted by catkin (Post 3611181)
while IFS=: read userName passWord userID groupID geCos homeDir userShell is perfectly correct but neither transparent nor efficient; its only merit is to save a line. Functionally this is identical
Code:

IFS=:
while read userName passWord userID groupID geCos homeDir userShell


I agree with all your other criticisms, but using while IFS=: read userName passWord userID groupID geCos homeDir userShell does have the advantage that IFS gets reset back to previous value after read command finishes.

Quote:

Originally Posted by Qu3ry
ntubski, the new script didn't work. There is no mp3 matches the pattern to begin with. I have changed the code to (ls *-*.mp3) to match the pattern and ran the code with the echo command instead of the cat command. It didn't work neither. Here is the result:

*.mp3.mp3 to mp3.mp3

where "to" is the replacement of ">"

It works for me::confused:
Code:

~/tmp/songs$ ls
01-1.song.mp3  01-2.song.mp3  01-3.pop.mp3  01-4.pop.mp3
~/tmp/songs$ for song in $(ls *-*.*.mp3 | cut -d. -f2 | sort -u) ; do
> echo *."$song".mp3 to "$song".mp3
> done
01-3.pop.mp3 01-4.pop.mp3 to pop.mp3
01-1.song.mp3 01-2.song.mp3 to song.mp3


catkin 07-18-2009 12:22 PM

Quote:

Originally Posted by ntubski (Post 3611912)
I agree with all your other criticisms, but using while IFS=: read userName passWord userID groupID geCos homeDir userShell does have the advantage that IFS gets reset back to previous value after read command finishes.

Thanks :) Interesting! How does that work? AIUI, it would only happen if "while" set up a subshell -- but, if that's the case, how are any variables set in the loop available in the parent shell?

ntubski 07-18-2009 03:24 PM

There is no subshell, assignments before a command are only in effect for that command.

Simple-Command-Expansion:
Quote:

If no command name results, the variable assignments affect the current shell environment. Otherwise, the variables are added to the environment of the executed command and do not affect the current shell environment.
I used this trick in the extracting floating numbers from variable using bash's builtin string chopping thread.

catkin 07-19-2009 06:47 AM

Quote:

Originally Posted by ntubski (Post 3612044)
There is no subshell, assignments before a command are only in effect for that command.

Simple-Command-Expansion

I used this trick in the extracting floating numbers from variable using bash's builtin string chopping thread.

Thanks for explaining inc. link and sorry for not appreciating your use of it at http://www.linuxquestions.org/questi...24#post3609924. Vague memories of reading about it are coming to mind along with deciding not to use it because it did not match legibility aspirations. All the same it's a valid technique and I should know about it, even if I choose not to use it.

Just for fun (and the upcoming obfuscated bash competition):
Code:

a=X b=Y c=Z env

Qu3ry 07-22-2009 02:29 AM

Sorry ntubski, I have given you the wrong info. There should be no dot between the numerals and the song names.

01-01 song with spaces in filename.mp3
01-02 song with spaces in filename.mp3


etc...

I have changed the script to (ls *-*\ *.mp3),but it didn't work.
Can you explain how does your "cut -d. -f2" work?

Qu3ry 07-22-2009 02:37 AM

BTW, Files I want to rename are scattered across different folders. for instance, they are all in different subfolders. How can I move them one directory up in order to rename them in batch?

Quote:

$ find Quantum/ -type f

Quantum/Disc 1/.directory
Quantum/Disc 1/01.avi
Quantum/Disc 1/02.avi
Quantum/Disc 1/03.avi
Quantum/Disc 1/04.avi
Quantum/Disc 1/05.avi
Quantum/Disc 1/06.avi
Quantum/Disc 2/.directory
Quantum/Disc 2/07.avi
Quantum/Disc 2/08.avi
Quantum/Disc 2/09.avi
Quantum/Disc 2/10.avi
Quantum/Disc 2/11.avi
Quantum/Disc 2/12.avi
Quantum/Disc 3/.directory
Quantum/Disc 3/13.avi
etc

EDIT: I have figured it out:
in working directory, type

Quote:

#find . -type f -exec mv {} . \;

ntubski 07-22-2009 06:59 PM

Quote:

Originally Posted by Qu3ry (Post 3616077)
Sorry ntubski, I have given you the wrong info. There should be no dot between the numerals and the song names.

01-01 song with spaces in filename.mp3
01-02 song with spaces in filename.mp3


etc...

I have changed the script to (ls *-*\ *.mp3),but it didn't work.
Can you explain how does your "cut -d. -f2" work?

"cut -d. -f2" assumes that the song name is separated from the numerals by dots, so of course it doesn't work. It extracts the text between the 1st and 2nd dots.

Here's a version that assumes the song name is the part that comes after the numbers and dashes. I had to rearrange the loop a bit to handle whitespace.
Code:

~/tmp/songs$ while read -d$'\n' song ; do
> echo *"$song" TO "$song" ; done < <(ls *.mp3 | sed 's/^[-0-9]* //' | sort -u)
01-01 a song.mp3 01-02 a song.mp3 TO a song.mp3
01-01 another song.mp3 01-02 another song.mp3 TO another song.mp3



All times are GMT -5. The time now is 12:57 AM.