I only use Bash for the simplest tasks. For anything more elaborate I use Groovy scripts. At some point I switch to full Groovy projects, switching on static compilation, adding unit and integration tests and so forth.
> At some point I switch to full Groovy projects, switching on static compilation
If you let your Apache Groovy scripts get too large before switching on static compilation, you often have to modify the types and logic in your programs before they'll even compile. This problem occurs because static compilation was only bolted onto Groovy for version 2.0 and there's an impedance mismatch between what's required for its dynamic and static modes.
The problem with python is even if you do it well, you don't get very far above a script. As oweiler above mentions, this is where languages with a bit more comprehensive support for true incremental typing, structuring etc. work better. It's definitely one of the things I like about Groovy - that it's both a better first line scripting language AND cuts it as a first class structured application development language on a par with Java etc - and you can do incremental shades of gray all the way in between. Of course, you have to actually DO it which is where the real trap is. But its definitely a level above Python where the friction of transitioning from "this is fine as an ad hoc script" to "This really ought to be a proper library / module in a statically typed language" is high enough that it will basically never happen.
I'm using Julia the same way for math computation: starting as a scripting language, but creating new types and adding typing information on the fly (start with a tuple, then create a struct if that tuple is useful multiple times).