Oh you're right, it'd need to take a param with the setup form(s) and somehow look up the corresponding cleanup form(s), sort of like getter and setter code registered with setf.
It's the difference between being Lisp and being a member of the Lisp Family (aka "being a Lisp"). It's a fun way to weed out 1-bit people from the population: http://www.xach.com/naggum/articles/3225130472400367@naggum.... Furthermore Common Lisp is a Lisp-2 whereas Scheme is a Lisp-1. (Aesthetically I prefer Scheme.) So really there are several working meanings at play when people say "Lisp", this is frustrating for 1-bit people.
My usual heuristic in using context clues to determine which meaning is meant: if there is no qualifier for a particular family member of Lisp, I assume something is talking about the Lisp family, unless accompanied by a code example, in which case it's probably Common Lisp. When I see someone type "LISP" I usually think "This person doesn't know Lisp", just like how you'll sometimes see people type FORTH or PERL when talking about those languages. But that's not always the case so it's useful to double-check before calling them out, especially if they give code examples. (They may just be old after all. ;) )
If you want to abstract the pattern out even further, then all you are doing is running a function within a certain context and then getting everything back to normal. Using under in J, scoping it out with ruby, or using with-open in Lisp are all examples of the pattern, just different ways of thinking about it.
I think what you're describing is somewhat different: temporarily changing context is like a let block. So for instance in CL you can rebind standard-output to a different stream using let, but then you have to do the legwork to ensure that everything is cleaned up if something goes wrong, while with-open-file or with-output-to-stream will do part of that work.
(with-open-file (output "foo.txt" :direction :output :if-exists :supersede) (print-foo output))