Reputation: 121
I am trying to write a simple bash script to allow me to rename some tv show filenames that were poorly formatted (and aren't picked up by xbmc). I was able to get the awk command to print out what I want the output to be, but as soon as I throw it in the backticks (or the accent character) so I can incorporate the mv command, it errors out.
The awk command I have working is as follows (there is a simple for loop above it to go through all the files in the directory).
echo $i | awk -F" " '{print $1 "\\ -\\ S0" $3 "E" $5 ".avi"}'
However, when I add the mv command to it,
mv $i `echo $i | awk -F" " '{print $1 "\\ -\\ S0" $3 "E" $5 ".avi"}'`
it errors out with the following.
awk: warning: escape sequence `\ ' treated as plain ` '
As far as I know, I need the backslashes in there for the mv command to work properly.
The input formats for the filenames are
Showname Season 1 Episode 01 - Episode Title.avi
Show Name Season 1 Episode 01 - Episode Title.avi
I want it to be
Showname - S01E01 - Episode Title.avi
Show Name - S01E01 - Episode Title.avi
I'm not too worried about the episode title being included (I couldn't find any simple way to include all the extra fields, and I have another program I use to move and name all my files anyway that will put the title there). There are some shows that have two words in their first name (so it could be Show Name), so my plan is to just adjust the script manually to work with the various fields.
Are there any suggestions on how to get awk to work properly with the filename to prevent the error from coming up when I use the "backticks"?
Upvotes: 0
Views: 1995
Reputation: 754280
Note that since your file names contain spaces (or, at least, the interesting ones do), you should be using double quotes around "$i"
in the mv
command, and in the echo
. You 'get away with it' in the echo if there are only single spaces and no tabs or newlines in the file names, but you destroy the file name if you don't enclose it in double quotes and it does contain two adjacent spaces, or leading spaces, or trailing spaces, or ... Generally, use $(...)
instead of back-quotes too.
Your question would be improved by giving a few (one or two) sample input file names, and the expected corresponding output file names. All we can tell is that when you split using spaces (the -F" "
hardly seems necessary), you have at least 5 parts to a file name, of which only the first, third and fifth are to be kept. You are presumably generating the backslashes in the output to preserve the spaces in the name, rather than because you think backslashes in the filename are a good idea. (That is, the backslashes won't appear in the names on disk.)
Since you have a loop around it, the way I'd do this with awk
is:
for file in *
do
name=$(echo "$file" | awk '{print $1 " - S0" $3 "E" $5 ".avi"}')
mv "$file" "$name"
done
There are solid reasons for doing it thus. The assignment preserves the spaces etc in the output of awk
(chopping the trailing newline, but that's all). The mv
command line is then trivial, using the double quotes to prevent problems.
Trying:
for file in *
do
mv "$file" $(echo "$file" | awk '{print $1 " - S0" $3 "E" $5 ".avi"}')
done
gets you into trouble as the output of the pipeline is rescanned by the shell and split and you have to protect against splitting (hence the backslashes in the original code) and worry about other shell metacharacters (what if a show's name has an apostrophe in it, for example).
However, you can enclose the whole of the $(...)
in double quotes, and then things work — I was going to say 'as expected' but actually it wasn't as I expected. But testing with both bash
and ksh
shows that this works:
for file in *
do
mv "$file" "$(echo "$file" | awk '{print $1 " - S0" $3 "E" $5 ".avi"}')"
done
The outer double quotes simply prevent the shell from expanding the output of the $(...)
, and the use of $(...)
greatly simplifies life. Simply replacing the $(...)
with back quotes yields all sorts of problems; inside the backticks, you have to escape all sorts of things. But it appears that the $(
in the $(...)
notation suspends the current double quoted string search and starts a search for the closing )
with 'normal' interpretation of what's in the middle, which gives the result required.
I think the two-line (assignment plus use) is simpler to understand, but this version with the complex command substitution inside double quotes is OK too.
Here is some test code that assumes you don't currently have a directory called spaced-out
in your current directory (and that you do have write permission in the current directory):
mkdir spaced-out
(
cd spaced-out
# Create files with spaces in the names
for file in "part1 x part2 y part3" "show1 a show2 b show3" \
" leader1 x leader2 y leader3 "
do
echo hello world > "$file"
done
ls
ls -l
# Rename the files
for file in *
do
mv "$file" "$(echo "$file" | awk '{print $1 " - S0" $3 "E" $5 ".avi"}')"
done
# Show results and clean up
ls -l
rm -f *
)
rmdir spaced-out
When run as a script (bash rename.script
), it generates:
leader1 x leader2 y leader3 part1 x part2 y part3 show1 a show2 b show3
total 24
-rw-r--r-- 1 jleffler eng 12 Jul 6 08:02 leader1 x leader2 y leader3
-rw-r--r-- 1 jleffler eng 12 Jul 6 08:02 part1 x part2 y part3
-rw-r--r-- 1 jleffler eng 12 Jul 6 08:02 show1 a show2 b show3
total 24
-rw-r--r-- 1 jleffler eng 12 Jul 6 08:02 leader1 - S0leader2Eleader3.avi
-rw-r--r-- 1 jleffler eng 12 Jul 6 08:02 part1 - S0part2Epart3.avi
-rw-r--r-- 1 jleffler eng 12 Jul 6 08:02 show1 - S0show2Eshow3.avi
When run as bash -x rename.script
, it generates:
+ mkdir spaced-out
+ cd spaced-out
+ for file in '"part1 x part2 y part3"' '"show1 a show2 b show3"' '" leader1 x leader2 y leader3 "'
+ echo hello world
+ for file in '"part1 x part2 y part3"' '"show1 a show2 b show3"' '" leader1 x leader2 y leader3 "'
+ echo hello world
+ for file in '"part1 x part2 y part3"' '"show1 a show2 b show3"' '" leader1 x leader2 y leader3 "'
+ echo hello world
+ ls
leader1 x leader2 y leader3 part1 x part2 y part3 show1 a show2 b show3
+ ls -l
total 24
-rw-r--r-- 1 jleffler eng 12 Jul 6 08:03 leader1 x leader2 y leader3
-rw-r--r-- 1 jleffler eng 12 Jul 6 08:03 part1 x part2 y part3
-rw-r--r-- 1 jleffler eng 12 Jul 6 08:03 show1 a show2 b show3
+ for file in '*'
++ echo ' leader1 x leader2 y leader3 '
++ awk '{print $1 " - S0" $3 "E" $5 ".avi"}'
+ mv ' leader1 x leader2 y leader3 ' 'leader1 - S0leader2Eleader3.avi'
+ for file in '*'
++ echo 'part1 x part2 y part3'
++ awk '{print $1 " - S0" $3 "E" $5 ".avi"}'
+ mv 'part1 x part2 y part3' 'part1 - S0part2Epart3.avi'
+ for file in '*'
++ echo 'show1 a show2 b show3'
++ awk '{print $1 " - S0" $3 "E" $5 ".avi"}'
+ mv 'show1 a show2 b show3' 'show1 - S0show2Eshow3.avi'
+ ls -l
total 24
-rw-r--r-- 1 jleffler eng 12 Jul 6 08:03 leader1 - S0leader2Eleader3.avi
-rw-r--r-- 1 jleffler eng 12 Jul 6 08:03 part1 - S0part2Epart3.avi
-rw-r--r-- 1 jleffler eng 12 Jul 6 08:03 show1 - S0show2Eshow3.avi
+ rm -f 'leader1 - S0leader2Eleader3.avi' 'part1 - S0part2Epart3.avi' 'show1 - S0show2Eshow3.avi'
+ rmdir spaced-out
Double-spaced file names, optionally with leading and/or trailing blanks, are often a very good tests for scripts that claim to work with file names that contain blanks. This code does not manage file names that contain newlines; the awk
script treats the one name as two lines of input, which is not what's wanted. It can be fixed; it usually isn't worth the trouble unless you need your script to be absolutely bomb-proof (for example, because it is going to customers — customers do the darnedest things and file names with newlines in them are well within their abilities, even though you'd never do it yourself).
Upvotes: 4
Reputation: 203807
Try this:
for file in *
do
set -- $file
mv -- "$i" "$(printf "%s - %s %sE%s.avi" "$1" "$file" "$3" "$5")"
done
It's a guess though - you need to show what your input and output file names actually look like, not just a command that doesn't work to convert them!
Upvotes: 1
Reputation: 121
Well, after more digging and toying around, I found out that I can just encompass the whole target of the mv command in quotes and then just remove the backslashes.
But then I came up with a different problem. It was saying that the target for the mv command wasn't a directory. I KNOW it isn't a directory... I'm trying to rename some files here... (grumble, grumble)
mv: target 'Showname - S01E01.avi' is not a directory
Anyway, after more googling I found out on a LinuxQuestions post that this is apparently due to the spaces in the filename. I was able to fix that by adding the following to the beginning of my script.
IFS='
'
Now it renames my files to a decent naming scheme so other programs/services can pick up on them and add them properly
Upvotes: 0