 |
 |
Replace string in item names including subdirectories?
|
 |
|
 |
|
Junior Member
Join Date: Feb 2001
Status:
Offline
|
|
I was wondering if anyone knew of a way to replace a certain string of text in the item names of every item in a directory, including subdirectories. This would include the name of the directories and subdirectories themselves. Apple has an applescript that does this for items in a single directory (no subdirectories) and they had one that worked in OS9 with subdirectores. (The OS9 one doesn't work in X. It makes a list of the paths of all item's in the directory, and once it changes the name of a subdirectory, it can't locate the files witin it.)
So anyway... I was wondering if anyone knew of a command line app built into OSX that could accomplish this task, or could create an applescript to the same effect.
Any help is appreciated. Thanks,
Brandon
|
|
|
| |
|
|
|
 |
|
 |
|
Dedicated MacNNer
Join Date: Jul 2001
Location: NC
Status:
Offline
|
|
Hi Brandon,
You didn't say what you wanted to replace so I had to just make a choice. I chose to replace the first "a" with "azz". If you replace the the regular expression, "a", and the replacement string, "azz", in the last "sub" command, you can make any replacement you please. This is a one-liner, albeit a long one, using find and awk. The awk part can be turned into an awk script if that's more convenient. Ain't UNIX impressive?
find -d . | xargs -n1 echo | awk 'BEGIN { FS="\n"} !/\.$/{ dir = $1; file = $1; sub("/[^/]*$","/",dir); sub("^.*/","",file); newfile = file; sub("a","azz",newfile); newfile = dir newfile; file = dir file; system( "mv " "\"" file "\" \"" newfile"\"")}'
|
|
Gary
A computer scientist is someone who, when told to "Go to Hell", sees the
"go to", rather than the destination, as harmful.
|
| |
|
|
|
 |
|
 |
|
Junior Member
Join Date: Feb 2001
Status:
Offline
|
|
Wow, that is impressive!
However, I think I'm having a little trouble. No file names are getting changed and I'm getting this error:
awk: syntax error at source line 1
context is
BEGIN { FS="\n"} >>> \ <<< .$/{ dir = $1; file = $1; sub("/[^/]*$","/",dir); sub("^.*/","",file); newfile = file; sub("a","azz",newfile); newfile = dir newfile; file = dir file; system( "mv " "\"" file "\" \"" newfile"\"")}
awk: bailing out at source line 1
xargs: echo terminated by SIGpipe
I'm assuming it has to be run from within the directory you're trying to modify, which I am. And I had files and folders in that directory named "a" ready to be changed, but no luck. Any Ideas?
Thanks for your help!
Brandon
[Edit: The first time I run it in any given terminal session, the error is simply this: "/: Event not found." Subsequent errors are as above]
(Last edited by BKuchta; Jan 18, 2003 at 03:02 AM.
)
|
|
|
| |
|
|
|
 |
|
 |
|
Dedicated MacNNer
Join Date: Jul 2001
Location: NC
Status:
Offline
|
|
Originally posted by BKuchta:
BEGIN { FS="\n"} >>> \ <<< .$/{
Hi Brandon,
I'm sorry it didn't work. I notice above the >>> \ <<<. I assume that the arrows are inserted by awk to "point" to the error. Notice that there are two symbols missing in front of the backslash. There should be an exclamation mark and a forward slash in front of the backslash. Maybe that's the problem. Did you use cut-and-paste to get it in the command line?
I developed the script in a gawk emulation of awk but then tested it with Apple's awk at the end and it worked. I guess my suggestion at this point would be to replace the system command at the end with a print command and tweak it. That's how I debugged it.
Unfortunately I have to leave in a little while to go out of town for the weekend. I'm sorry I "shipped my software at a time when I couldn't provide better support" but hey, ya' gets whatchya pay for!
I will naturally check in when I get back. At least you have an idea of what's possible. I think the main difference between this and what you tried is the "-d" switch which instructs find to do a depth first search. That avoids the problem of being unable to reference the contents of a directory after it has been renamed; the contents are renamed first. I'm sure with that, and this awk code as a pointer, you can come up with something that works!
|
|
Gary
A computer scientist is someone who, when told to "Go to Hell", sees the
"go to", rather than the destination, as harmful.
|
| |
|
|
|
 |
|
 |
|
Dedicated MacNNer
Join Date: Jul 2001
Location: NC
Status:
Offline
|
|
Hi Brandon,
Maybe a straightforward shell script would be more useful. At least you wouldn't have to know awk to fix it if it doesn't work. If you cut-and-paste, note that non-breaking spaces were used for indention and some browsers copy those to characters other than spaces. If you simply change the indention to tabs, all should be well. This is written for Jaguar's sh, i.e., bash. I hope it works. Oh, the old and replacement strings are now arguments, as is the starting directory. I hope this works for you:
#!/bin/sh
version="v0.0.0.5"
#################################################
# batch_rename v0.0.0.5 for use with Darwin-OSX ......
# by Gary Kerbaugh
#
# batch_rename replaces old_expression with replacement_string
# for every file and directory in starting_directory
#################################################
NUM_ARGS=3
E_BADARGS=65 # Exit value if incorrect number of args passed.
USAGE_TEXT="`basename $0` $version:
required arguments: old_expression replacement_string starting_directory
batch_rename replaces old_expression with replacement_string
for every file and directory in starting_directory"
##-----
# Main
##-----
if [ $# -lt "$NUM_ARGS" ]; then # Script invoked without three command-line args?
echo "Usage: `basename $0` old_expression replacement_string starting_directory"
echo "$USAGE_TEXT"
exit $E_BADARGS
fi
oldexp="$1"
shift
newstr="$1"
shift
for argu in "$@"
do
oldIFS=$IFS
IFS='
'
for elem in $(find -d "$argu")
do
IFS=$oldIFS
dir=$(dirname "$elem")"/"
oldfile=$(basename "$elem")
newfile=$(echo $oldfile | sed "s/$oldexp/$newstr/")
oldfile="$dir""$oldfile"
newfile="$dir""$newfile"
if [ "$oldfile" != "$newfile" ]; then
echo "Renaming $oldfile to $newfile"
mv "$oldfile" "$newfile"
fi
done
done
(Last edited by Gary Kerbaugh; Jan 18, 2003 at 01:33 PM.
)
|
|
Gary
A computer scientist is someone who, when told to "Go to Hell", sees the
"go to", rather than the destination, as harmful.
|
| |
|
|
|
 |
|
 |
|
Junior Member
Join Date: Feb 2001
Status:
Offline
|
|
Wow, Thanks!
The shell script works great with one exception, it doesn't like file/folder names with spaces in them. Any ideas?
I figured out why the awk line was giving me a different error the second time. I'd hit the up arrow to try it again (assuming it would be the same command) and the command would appear, but with "!/" missing. Weird. Any idea what the "/: Event not found." Could mean?
Thanks!
Brandon
|
|
|
| |
|
|
|
 |
|
 |
|
Dedicated MacNNer
Join Date: Jul 2001
Location: NC
Status:
Offline
|
|
Originally posted by BKuchta:
The shell script works great with one exception, it doesn't like file/folder names with spaces in them. Any ideas?
Hi Brandon,
Rats! I thought I tested for that; I guess not. I must be remembering tests of the awk script. The shell script is 90% "space proof" because that's something I always consider. The problem is probably in the assignment of the "oldfile" or the "newfile". Quoting is what's necessary but those assignments don't have quotes around the entire right side because there are quotes inside. I'm sure I can fix it but I'm out of town right now. I have access to the internet but not to a Mac. (I'm on Windoze now) I'll be back Monday night so I should have a fix by Tuesday sometime.
One thought, you could try putting the line:
IFS=$oldIFS
at the bottom of the loop. There are things that could mess up but none jump out at me in this script. Give it a try and see what happens.
I could think about it before then if you post the output of a problem file. What I will do is use the "poor man's debugger". I'll run the script with a bunch of echo statements to figure out where the problem occurs. That is, where the filenames are broken into two arguments because of the spaces. If you get a chance to experiment and pinpoint the problematic statement, I might be able to suggest a fix from here. I'll monitor this thread.
Any idea what the "/: Event not found." Could mean?
Generally before each code block, (in braces) awk has a pattern that is tested against each record for a match. Without the forward slash, that pattern isn't a complete regular expression. I don't know to what "event" refers specifically but that's the problem.
By the way, if the changes you want to make are at the end of the filename then that can be done in a short one-liner. This one is a little longer because it must strip and hold the directory name.
|
|
Gary
A computer scientist is someone who, when told to "Go to Hell", sees the
"go to", rather than the destination, as harmful.
|
| |
|
|
|
 |
|
 |
|
Junior Member
Join Date: Feb 2001
Status:
Offline
|
|
Well I think I figured out why the shell script won't work with file names with spaces. It looks like the list generated in the for loop by find is delimited by spaces. Thus when I do an echo "$elem" at the start of the loop, I'll get only the fist part of the file name, that which is before the space. The rest of the file name will appear next. And if I put quotes around "$(find -d "$argu")" it returns the results as one long string.
Is there any way to delimit the list by newline character?
I'm pretty new at shell scripting, maybe I'm missing something obvious.
Actually, if I could find a way to get the awk line working that would probably be best. This is going to be part of an AppleScript Studio app I'm making. Although, I could just put the shell script in the application package and call if from there.
Thanks for your help, I really appreciate it!
Brandon
|
|
|
| |
|
|
|
 |
|
 |
|
Dedicated MacNNer
Join Date: Jul 2001
Location: NC
Status:
Offline
|
|
Hi Brandon,
Actually, the output of find is supposed to be delimited by newline. That's the point of setting the IFS environment variable in the preceding line. (naturally I thought of that one) That is Bash's "Input Field Separator". Jaguar uses Bash to implement the Bourne Shell, sh. Puma (pre-Jaguar) used zsh-3.0.3 (old) for that purpose. I'm not sure if the corresponding variable was named "IFS". You might try setting the "she-bang" top line to:
#!/bin/bash
in case your client is not running Jaguar.
You should also double check to see that there hasn't been another copy-and-paste error. In the text editor you use to edit your scripts, place the cursor in front of the single quote on the line that is an isolated single quote and hit backspace. (Erase the newline, and cursor will jump up to preceding line.) Then hit return to make it look like it did before. That should guarantee that the quotes quote just a newline character.
The one line part is irrelevant; you can put almost any script on one line (no matter how long) by using semi-colons, ";", in place of real newlines at the ends of lines. The awk script I gave you is also really a multiline script, I just used semi-colons, as described above, to "cram" it all on one line. The real issue is which solution works for all versions of MacOS X. They all have bash and awk but tests would have to be made to determine which versions of bash and awk supported which features in the script. I'll post the "one line" bash script tomorrow but I want to wait until I've tested a bit before I do that and I won't be back to my mac until tonight.
(Last edited by Gary Kerbaugh; Jan 20, 2003 at 11:06 AM.
)
|
|
Gary
A computer scientist is someone who, when told to "Go to Hell", sees the
"go to", rather than the destination, as harmful.
|
| |
|
|
|
 |
|
 |
|
Junior Member
Join Date: Feb 2001
Status:
Offline
|
|
Yup, you were right, copy and paste error. There was an extra space in with the newline getting assigned to IFS. 
|
|
|
| |
|
|
|
 |
|
 |
|
Dedicated MacNNer
Join Date: Jul 2001
Location: NC
Status:
Offline
|
|
Originally posted by BKuchta:
Yup, you were right, copy and paste error. There was an extra space in with the newline getting assigned to IFS.
Hi Brandon,
Great! So it's working now, right? A space in IFS is what the assignment is trying to avoid so it makes sense that would cause a problem. When I got back, I saw that I did test it with directories that had spaces so it should work.
I did have some trouble putting the script on one line. It works but not as well as the script so the script is probably the best way to go.
The awk script should work as well. However, there may some extraneous spaces in it as well, so check it carefully. They tend to appear at the ends of lines and even though it's a one-liner, there may be spaces where it wraps. Anyway, you know have something that work, which is the main thing.
|
|
Gary
A computer scientist is someone who, when told to "Go to Hell", sees the
"go to", rather than the destination, as harmful.
|
| |
|
|
|
 |
|
 |
|
Dedicated MacNNer
Join Date: Jul 2001
Location: NC
Status:
Offline
|
|
Hi Brandon,
OK, you want a one-liner, how's this?
find -d /<top>/<directory> | sed -e 's/\(.*\/\)\([^\/]*\)a\([^\/]*\)$/mv "&" "\1\2azz\3"/' | sh
|
|
Gary
A computer scientist is someone who, when told to "Go to Hell", sees the
"go to", rather than the destination, as harmful.
|
| |
|
|
|
 |
|
 |
|
Junior Member
Join Date: Feb 2001
Status:
Offline
|
|
Originally posted by Gary Kerbaugh:
Hi Brandon,
OK, you want a one-liner, how's this?
find -d /<top>/<directory> | sed -e 's/\(.*\/\)\([^\/]*\)a\([^\/]*\)$/mv "&" "\1\2azz\3"/' | sh
Wow. That's cool! How the heck did you come up with that?
It does have the odd side effect of trying to execute my .DS_store files. But that won't hinder it for my uses.
You're great Gary... someday I hope to posess 1/10 of the knowlege you do.
Brandon
|
|
|
| |
|
|
|
 |
|
 |
|
Dedicated MacNNer
Join Date: Jul 2001
Location: NC
Status:
Offline
|
|
Originally posted by BKuchta:
Wow. That's cool! How the heck did you come up with that?
Hi Brandon,
I can answer that. This takes advantage of ability of sed to reference pieces of the match. (a capability shared by gawk and Perl) I matched the entire filename, including the path. The (escaped) parentheses specify the pieces of the path to be referenced. The \(.*\/\) matches the part of the path up to the last slash, i.e., the path. The two occurrences of \([^\/]*\), matching anything but a slash, match the part of the filename before and after the "a".
The replacement string builds the move command, starting with mv. The "&" is a reference to the entire match, thus writes in the original path and filename. \1 is a reference to the first part of the match, the path. \2 is a reference to the second part of the match, the part of the filename preceding the "a". "azz" replaces the "a" with "azz" and the \3 is a reference to the third part of the match, the part of the filename after the "a". Thus, "\1\2azz\3" builds the new filename in the mv command. Then the fully constructed command is then piped to sh for execution.
The construction of a command as text at runtime is a powerful capability of the shell that isn't easy to duplicate in many programming languages. Java created an elaborate reflection mechanism to support this capability. The idea to use this construction came from a spectacular book, Unix Power Tools, 3rd Edition. I highly recommend it.
(Last edited by Gary Kerbaugh; Jan 22, 2003 at 07:38 PM.
)
|
|
Gary
A computer scientist is someone who, when told to "Go to Hell", sees the
"go to", rather than the destination, as harmful.
|
| |
|
|
|
 |
|
 |
|
Fresh-Faced Recruit
Join Date: Jul 2002
Location: brussels/belgium/eu
Status:
Offline
|
|
Thanks Brandon to bring up the question
and Gary to come up with the solution !
I can't wait till tomorrow to test it (i do not have the mac at home, but i'm sure it'll work!)
if you're a heavy beer drinker I owe you a good one !
"Belgium is also the land of strong beers re-fermented in the bottle. Innocent-looking beers like the golden Duvel and Trappist Westmalle Tripel pack eight or nine percent alcohol. The Trappists' delights range from the intensely bitter Orval to the sweetish, chocolately, Rochefort 10. Other countries have more breweries, but none has beers so distinctive, diverse, idiosyncratic as Belgium." from www.beerhunter.com
cheers
cardinal from brussels/belgium
|
|
|
| |
|
|
|
 |
 |
|
 |
|
|
|
|
|

|
|
 |
Forum Rules
|
 |
 |
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
|
HTML code is Off
|
|
|
|
|
|
 |
 |
 |
 |
|
 |
|