[SOLVED] Bash Script Automation for new Subdirectories.
ProgrammingThis forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
I'm producing game-related videos, and I've made 2 scripts to make my work much easier. Lets call them Script A and Script B.
Script A run several copies of Script B (one in each subdirectory). And every time I have new directories, I manually copy Script B into them, and then edit Script A so that the pathways for the new Script Bs are correct.
Sooo, I'd like help in remaking Script A so that it doesn't need editing each time new subdirectories are added.
Right now Script A looks something like this.
Code:
#!/bin/bash
cd Directory-01
./Script-B.sh
cd ..
cd Directory-02
./Script-B.sh
cd ..
cd Directory-03
./Script-B.sh
cd ..
#etc...
It's a simple script that just changes directory, run Script-B.sh in that directory, then goes to the parent directory and up the next subdirectory and runs Script-B.sh in that directory, and so on. Worth mentioning, the actual directories have more unique names, and each time I run the script the amount of subdirectories varies.
So what I need for my new Script A, is that it copies Script-B.sh into all the current subdirectories(I normally do this manually), then executes Script-B.sh in each of these subdirectories.
I'd also like help with Script B, right now, it outputs a standardised name as "Final-Video.mov" in the subdirectory the script is ran from. I'd really appreciate it if I could end the script with something like
Code:
mv Final-Video.mov ../$1.mov
rm -r -f $1
where $1 is the name of the current subdirectory, essentially naming the file after the Subdirectory it's in, moving it out of the subdirectory and then removing the subdirectory once the script is done. But how to get $1 to hold the name of the current child-directory?
As far as all of your subdirectories, you can use a for loop, and loop through all file (*), and then do what you need to do. As far as "script B," what exactly does it do? Maybe you can share it with us, and possible find a completely better way to tackle what you are trying to accomplish.
But how to get $1 to hold the name of the current child-directory?
pwd will tell you the path to the current working directory. Therefore, basename `pwd` will tell you the name of the current directory, and dirname `pwd` the path to the parent directory (logically the same as .. ).
Quote:
Originally Posted by Valpskott
Script A run several copies of Script B (one in each subdirectory). And every time I have new directories, I manually copy Script B into them, and then edit Script A so that the pathways for the new Script Bs are correct.
Don't do that. Replace them with Script C, that takes one or more (target) directory names as parameters. Then you can do
or, if you have a very powerful machine, and want to do all directories in parallel,
Code:
find tree-containing-directories -mindepth 1 -depth -type d -print0 | bash -c 'while read -d "" DIR ; do path-to-Script-C "$DIR" & ; done ; wait'
No need to do any copying or setup by hand. Note that the -depth parameter above tells find to supply the deepest directories first; this way you'll always get all child directories before their parents, and you can freely nest the directories.
The basic structure of Script C could be something like the following:
Code:
#!/bin/bash
while [ $# -gt 0 ]; do
dir="$1"
name=$(basename "$1")
shift 1
if (
cd "$dir" || exit $?
# Current working directory is the one to work on.
# "$name" is its name (not path).
# Use 'exit N' to cancel work.
# Does the final movie exist yet?
if [ ! -e "final.ogg" ]; then
# ... Work ...
fi
# Verify we now have "final.ogg" in the current directory.
[ -e "final.ogg" ] || exit 1
# Make sure nobody changed the directory name from under us.
[ "$dir" -ef . ] || exit 1
# Copy the final movie to parent directory, using the
# name of the current directory, but with .ogg suffix.
mv -f "final.ogg" "../$name.ogg" || exit $?
# Remove this child directory.
cd ..
rm -rf "$name"
) ; then
echo "$dir: Replaced with '$name.ogg' successfully." >&2
else
echo "$dir: Processing aborted due to an error." >&2
exit 1
fi
done
with # ... Work ... obviously replaced with the code you use to convert your input files into the final movie.
If you change a directory name while the script is still working, it will abort. You can then safely rerun the command. If the final movie already exists, the script will not regenerate it, just move (and rename) it.
Last edited by Nominal Animal; 11-06-2011 at 03:14 PM.
As far as all of your subdirectories, you can use a for loop, and loop through all file (*), and then do what you need to do. As far as "script B," what exactly does it do? Maybe you can share it with us, and possible find a completely better way to tackle what you are trying to accomplish.
So, when script B is done... the Subdirectory is still there, and it contains the original videofiles + Final-Video.mov. So, if I have like 10 subdirectories with videos, I will have 10 videos with the name Final-Video.mov (one in each Subdirectory). The name of the Subdirectory is descriptive of what the video inside it, so thats why I'd like to name "Final-Video.mov" after the directory it's in.
pwd will tell you the path to the current working directory. Therefore, basename `pwd` will tell you the name of the current directory, and dirname `pwd` the path to the parent directory (logically the same as .. ).
Don't do that. Replace them with Script C, that takes one or more (target) directory names as parameters. Then you can do
or, if you have a very powerful machine, and want to do all directories in parallel,
Code:
find tree-containing-directories -mindepth 1 -depth -type d -print0 | bash -c 'while read -d "" DIR ; do path-to-Script-C "$DIR" & ; done ; wait'
No need to do any copying or setup by hand. Note that the -depth parameter above tells find to supply the deepest directories first; this way you'll always get all child directories before their parents, and you can freely nest the directories.
The basic structure of Script C could be something like the following:
Code:
#!/bin/bash
while [ $# -gt 0 ]; do
dir="$1"
name=$(basename "$1")
shift 1
if (
cd "$dir" || exit $?
# Current working directory is the one to work on.
# "$name" is its name (not path).
# Use 'exit N' to cancel work.
# Does the final movie exist yet?
if [ ! -e "final.ogg" ]; then
# ... Work ...
fi
# Verify we now have "final.ogg" in the current directory.
[ -e "final.ogg" ] || exit 1
# Make sure nobody changed the directory name from under us.
[ "$dir" -ef . ] || exit 1
# Copy the final movie to parent directory, using the
# name of the current directory, but with .ogg suffix.
mv -f "final.ogg" "../$name.ogg" || exit $?
# Remove this child directory.
cd ..
rm -rf "$name"
) ; then
echo "$dir: Replaced with '$name.ogg' successfully." >&2
else
echo "$dir: Processing aborted due to an error." >&2
exit 1
fi
done
with # ... Work ... obviously replaced with the code you use to convert your input files into the final movie.
If you change a directory name while the script is still working, it will abort. You can then safely rerun the command. If the final movie already exists, the script will not regenerate it, just move (and rename) it.
I found that "basename `pwd`" didn't handle spaces too well.
Code:
filer@nellie:~/Video Edit/custom$ basename `pwd`
Video
Also, will the following line run Script C from within the Subdirectories, even if the path to Script C is in the parent? (no need to copy it to subdirectory?)
Nominal Animal, you are bash script crazy!
Tried your solution and it worked. Thank you very much!
Also, thank you all for taking your time to assist me.
I'll mark this thread as solved, but I'm still curious of how the PWD thing works and how variables work, and what the correct syntax is between the two.
Would this be correct
Code:
$mydir=basename "`pwd`"
echo $mydir
Would that print the name of the directory I'm in? Or what is a way to load variable $mydir with the name of the current dir(not complete path) I'm in?
Would that print the name of the directory I'm in? Or what is a way to load variable $mydir with the name of the current dir(not complete path) I'm in?
No, that's incorrect.
Let's go step by step, starting with basename. And as an example we'll even use a filename with, not one, but three spaces in it.
Code:
$ basename /home/david/test/my file.txt
my
Hmm, didn't work. What happened is that the shell broke the filename up into two arguments "/home/david/test/my" and "file.txt". Since basename only accepts one argument, the second part was dropped.
So we quote the filename:
Code:
$ basename "/home/david/test/my file.txt"
my file.txt
This forces the shell to treat the entire string as a single unit, rather than breaking it up at whitespace. See the links below for details.
Now, we want to create a variable to store that value in. First of all, the syntax for setting a variable is very simply name=value. You don't put a $ in front of the name when setting it, only when retrieving the value. And there are no spaces around the equals sign either.
Let's try to set it directly first:
Code:
$ name=my file.txt
bash: file.txt: command not found
Whoops! We had the same word-splitting problem again, and it thinks "file.txt" is meant to be a second command. And of course the solution is again to quote the string.
Code:
$ name="my file.txt"
Now if we want to insert the output of one command into another command we use command substitution. This is either `..` or $(..), with the latter being much preferred.
In this case we insert the output of basename into the variable setting.
Notice how I put a second level of quotes around the "$(..)". This ensures that the output is treated as a single unit, just as with the literal string.
(Actually in this specific situation quoting isn't needed, as substitutions get copied over without word-breaking, but it doesn't hurt, and they are necessary elsewhere).
Now let's echo back the variable to check its value.
Code:
$ echo $name
my file.txt
What happened to the extra spaces? Well, without quotes the shell again broke the output up into separate arguments, and echo got passed two separate strings, "my" and "file.txt", which it printed separated by a single space.
Code:
$ echo "$name"
my file.txt
That's better.
In fact, let's do something funky and try passing the output of basename directly to echo.
Code:
$ echo $( basename "/home/david/test/my file.txt" )
my file.txt
$ echo "$( basename "/home/david/test/my file.txt" )"
my file.txt
Just as with the variable, the quotes protect the string formatting.
So as you can see, it's vital to understand how the shell handles arguments and whitespace. Read and absorb these three links.
And don't forget the suggestion in my last post. $PWD is a variable automatically set by the shell that holds the full path to the current directory, in the same format as the pwd command.
And built-in parameter substitution rules can take the place of the external basename and pathname commands.
Code:
$ name="/home/david/test/my file.txt"
$ echo "$name"
/home/david/test/my file.txt
$ echo "${name%/*}" # strips off everything from the end to the last "/"
/home/david/test
$ echo "${name##*/}" # strips off everything from the beginning to the last "/"
my file.txt
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.