* zmv exits from function @ 2023-12-30 17:42 Ray Andrews 2023-12-30 20:38 ` Bart Schaefer 2023-12-30 22:34 ` Bart Schaefer 0 siblings, 2 replies; 25+ messages in thread From: Ray Andrews @ 2023-12-30 17:42 UTC (permalink / raw) To: Zsh Users I'm using zmv to rename files in a directory tree recursively via a 'for' loop which visits each subdirectory. It works fine if there are files found to rename, but if not, then the entire function crashes back to CL. How can I persuade zmv to just let the function cycle to the next subdir? I see no option that seems relevant. Cut down to the essentials: function global () { f ,dB >! /tmp/global_tmp # My function. Gives list of subdirs, works fine. curdir=$PWD for aa in $(cat /tmp/global_tmp); do # For every line: cd $aa zmv '(*).SNT' '$1.eml' zmv '(*).MES' '$1.eml' cd $curdir done } Lots of other test commands in place of zmv cycle fine in case of no match, but zmv insists on returning. ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2023-12-30 17:42 zmv exits from function Ray Andrews @ 2023-12-30 20:38 ` Bart Schaefer 2023-12-30 21:02 ` Ray Andrews ` (2 more replies) 2023-12-30 22:34 ` Bart Schaefer 1 sibling, 3 replies; 25+ messages in thread From: Bart Schaefer @ 2023-12-30 20:38 UTC (permalink / raw) To: Ray Andrews; +Cc: Zsh Users [-- Attachment #1: Type: text/plain, Size: 923 bytes --] On Sat, Dec 30, 2023 at 9:43 AM Ray Andrews <rayandrews@eastlink.ca> wrote: > > I'm using zmv to rename files in a directory tree recursively via a > 'for' loop which visits each subdirectory. It works fine if there are > files found to rename, but if not, then the entire function crashes back > to CL. How can I persuade zmv to just let the function cycle to the > next subdir? zmv forces zsh emulation which means the nomatch option is in effect, and you can't change that without editing the zmv source. Just run the zmv in a subshell: ( zmv '(*).SNT' '$1.eml' ) ( zmv '(*).MES' '$1.eml' ) Also make sure that you don't have the errr_exit or err_return setopts in effect or the loop will break even with the subshells. Alternately you could test whether the pattern matches any files before calling zmv in the first place. Arguably zmv could use null_glob. Thoughts from -workers? [-- Attachment #2: zmv-nullglob.txt --] [-- Type: text/plain, Size: 513 bytes --] diff --git a/Functions/Misc/zmv b/Functions/Misc/zmv index 269fe5ba5..51c8ad3ab 100644 --- a/Functions/Misc/zmv +++ b/Functions/Misc/zmv @@ -236,12 +236,14 @@ if [[ $pat = (#b)(*)\((\*\*##/)\)(*) ]]; then else fpat=$pat fi -files=(${~fpat}) +files=(${~fpat}(#qN)) [[ -n $hasglobqual ]] && pat=$opat errs=() +[[ -n $files ]] || errs=( "no files matched \`$fpat'" ) + for f in $files; do if [[ $pat = (#b)(*)\(\*\*##/\)(*) ]]; then # This looks like a recursive glob. This isn't good enough, ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2023-12-30 20:38 ` Bart Schaefer @ 2023-12-30 21:02 ` Ray Andrews 2023-12-30 22:21 ` Bart Schaefer 2023-12-30 21:15 ` Mikael Magnusson 2024-01-02 11:50 ` Peter Stephenson 2 siblings, 1 reply; 25+ messages in thread From: Ray Andrews @ 2023-12-30 21:02 UTC (permalink / raw) To: zsh-users [-- Attachment #1: Type: text/plain, Size: 601 bytes --] On 2023-12-30 12:38, Bart Schaefer wrote: > Just run the zmv in a subshell: > > ( zmv '(*).SNT' '$1.eml' ) > ( zmv '(*).MES' '$1.eml' ) > Perfect :-) > Arguably zmv could use null_glob. Thoughts from -workers? That's that option that makes everything grind to a halt if nothing if found? Think I recall that " (N) " setting to cope with it? From a users point of view it just makes trouble where no trouble really exists. BTW, I did look at the source and noticed no return values, I had thought that something like ' zmv ... && echo "Success" || echo "Nope"' ... might catch it. [-- Attachment #2: Type: text/html, Size: 1272 bytes --] ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2023-12-30 21:02 ` Ray Andrews @ 2023-12-30 22:21 ` Bart Schaefer 0 siblings, 0 replies; 25+ messages in thread From: Bart Schaefer @ 2023-12-30 22:21 UTC (permalink / raw) To: Ray Andrews; +Cc: zsh-users On Sat, Dec 30, 2023 at 1:02 PM Ray Andrews <rayandrews@eastlink.ca> wrote: > > > On 2023-12-30 12:38, Bart Schaefer wrote: > > > > Arguably zmv could use null_glob. Thoughts from -workers? > > That's that option that makes everything grind to a halt if nothing if found? No, you're thinking of nomatch, which as I said is already in effect and causing the result that you see. The null_glob option supersedes no_match and makes globs that don't match anything disappear without an error. This is in contrast to the no_nomatch option which leaves the glob un-expanded but does not remove it. Setting null_glob globally can be dangerous because it can leave a command with no arguments at all or with its arguments in a different order than expected. > Think I recall that " (N) " setting to cope with it? The (N) glob qualifier turns ON null_glob to override no_match, yes. That's the suggested amendment to zmv for which I'm asking -workers opinion. ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2023-12-30 20:38 ` Bart Schaefer 2023-12-30 21:02 ` Ray Andrews @ 2023-12-30 21:15 ` Mikael Magnusson 2023-12-31 3:43 ` Ray Andrews 2024-01-02 11:50 ` Peter Stephenson 2 siblings, 1 reply; 25+ messages in thread From: Mikael Magnusson @ 2023-12-30 21:15 UTC (permalink / raw) To: Bart Schaefer; +Cc: Ray Andrews, Zsh Users On 12/30/23, Bart Schaefer <schaefer@brasslantern.com> wrote: > On Sat, Dec 30, 2023 at 9:43 AM Ray Andrews <rayandrews@eastlink.ca> wrote: >> >> I'm using zmv to rename files in a directory tree recursively via a >> 'for' loop which visits each subdirectory. It works fine if there are >> files found to rename, but if not, then the entire function crashes back >> to CL. How can I persuade zmv to just let the function cycle to the >> next subdir? > > zmv forces zsh emulation which means the nomatch option is in effect, > and you can't change that without editing the zmv source. > > Just run the zmv in a subshell: > > ( zmv '(*).SNT' '$1.eml' ) > ( zmv '(*).MES' '$1.eml' ) You can also do this: { zmv '(*).SNT' '$1.eml' } always { TRY_BLOCK_ERROR=0 } I have this aliased as alias always_continue='always { TRY_BLOCK_ERROR=0 }' which lets you do the slightly neater { zmv '(*).SNT' '$1.eml' } always_continue -- Mikael Magnusson ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2023-12-30 21:15 ` Mikael Magnusson @ 2023-12-31 3:43 ` Ray Andrews 2023-12-31 3:58 ` Bart Schaefer 0 siblings, 1 reply; 25+ messages in thread From: Ray Andrews @ 2023-12-31 3:43 UTC (permalink / raw) To: zsh-users [-- Attachment #1: Type: text/plain, Size: 2083 bytes --] On 2023-12-30 13:15, Mikael Magnusson wrote: > You can also do this: > { zmv '(*).SNT' '$1.eml' } always { TRY_BLOCK_ERROR=0 } Sheesh, of course I'm no expert, still I've never seen any such construction before. Always new domains of syntax to study. So when zmv quits, crashing out of the function is not immediate -- there is further parsing, thus this 'always' word is handled. Interesting. On 2023-12-30 12:38, Bart Schaefer wrote: > Arguably zmv could use null_glob. Thoughts from -workers? That's that option that makes everything grind to a halt if nothing if found? > No, you're thinking of nomatch, which as I said is already in effect and causing the result that you see. The null_glob option supersedes no_match and makes globs that don't match anything disappear without an error. Yeah, for some unknown reason I said it backwards -- I know that (N) smooths continuation of flow, it doesn't break it. > This is in contrast to the no_nomatch option which leaves the glob un-expanded but does not remove it. I've run into that, and it can be ugly -- literal asterisks being fed to some command that doesn't want them. It's good to understand what's going down there. Setting null_glob globally can be dangerous because it can leave a command with no arguments at all or with its arguments in a different order than expected. Yes, another potential landmine. What might be intuitive is some way of simply making a command 'stop', but that might not be so simple -- something that holds it's place, but has no value -- it 'counts' as an argument but has no content. It's so dumb when: "ls no-such-files.*" ends up listing everything in the directory rather than nothing. Dunno who thought that was a good idea. > zmv -n '(*/)#(*).(SNT|MES)' '$1$2.eml' If that looks like it would do the right thing, remove the "-n". I'll give it a go. So much can be done with all that globbing functionality. Such power. But I can never remember that tersest of all terse syntaxes -- gotta make myself a cheat sheet including a page full of examples. [-- Attachment #2: Type: text/html, Size: 2860 bytes --] ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2023-12-31 3:43 ` Ray Andrews @ 2023-12-31 3:58 ` Bart Schaefer 2023-12-31 15:53 ` Ray Andrews 0 siblings, 1 reply; 25+ messages in thread From: Bart Schaefer @ 2023-12-31 3:58 UTC (permalink / raw) To: Ray Andrews; +Cc: zsh-users On Sat, Dec 30, 2023 at 7:43 PM Ray Andrews <rayandrews@eastlink.ca> wrote: > > { zmv '(*).SNT' '$1.eml' } always { TRY_BLOCK_ERROR=0 } > > So when zmv quits, crashing out of the function is not immediate -- there is further parsing No, the parsing is all done first: "{ ... } always { ... }" is a full expression. Zsh "knows" the always-part is coming before it even starts executing the first block. ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2023-12-31 3:58 ` Bart Schaefer @ 2023-12-31 15:53 ` Ray Andrews 2023-12-31 21:44 ` Bart Schaefer 0 siblings, 1 reply; 25+ messages in thread From: Ray Andrews @ 2023-12-31 15:53 UTC (permalink / raw) To: zsh-users [-- Attachment #1: Type: text/plain, Size: 635 bytes --] On 2023-12-30 19:58, Bart Schaefer wrote: > No, the parsing is all done first: "{ ... } always { ... }" is a full > expression. Zsh "knows" the always-part is coming before it even > starts executing the first block. > So that's multi-pass parsing. Anyway, first I've seen of anything like it. I'm reading up on it right now. 'always' is a reserved word then. Bottom line is that I now have a tool for handling 'no files' situations. Good to know because there could be any number of housekeeping functions that might want to clean up or rename some group of files if they exist but it's not a problem if they don't. [-- Attachment #2: Type: text/html, Size: 1368 bytes --] ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2023-12-31 15:53 ` Ray Andrews @ 2023-12-31 21:44 ` Bart Schaefer 2023-12-31 22:06 ` Ray Andrews 0 siblings, 1 reply; 25+ messages in thread From: Bart Schaefer @ 2023-12-31 21:44 UTC (permalink / raw) To: Ray Andrews; +Cc: zsh-users On Sun, Dec 31, 2023 at 7:53 AM Ray Andrews <rayandrews@eastlink.ca> wrote: > > So that's multi-pass parsing. Again, no. It's only parsed once, into an internal format (essentially, the same format used for "zcompile" files) and then executed if syntactically correct. The only way to get another parse is to use "eval". Expansion and word splitting on can occur during execution, but that doesn't change the syntax tree. Parsing does happen inside command substitution but is limited to that syntactic scope. ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2023-12-31 21:44 ` Bart Schaefer @ 2023-12-31 22:06 ` Ray Andrews 2024-01-02 14:51 ` Mark J. Reed 0 siblings, 1 reply; 25+ messages in thread From: Ray Andrews @ 2023-12-31 22:06 UTC (permalink / raw) To: zsh-users On 2023-12-31 13:44, Bart Schaefer wrote: > On Sun, Dec 31, 2023 at 7:53 AM Ray Andrews <rayandrews@eastlink.ca> wrote: >> So that's multi-pass parsing. > Again, no. It's only parsed once I don't have the background to understand it and it's probably not worth your time to make it clear. It 'look's like' a multiple parsing -- before zmv executes, it knows that errors will be handled differently than default due to following code, and then it 'goes back' and actually executes. Or ... zmv returns innocently and then the next code rescues the function from aborting? Nevermind, I don't need to know. It's just sorta fuzzy trying to understand at exactly what point the function returns if zmv isn't happy. I'm thinking in C naturally. ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2023-12-31 22:06 ` Ray Andrews @ 2024-01-02 14:51 ` Mark J. Reed 2024-01-02 17:01 ` Ray Andrews 0 siblings, 1 reply; 25+ messages in thread From: Mark J. Reed @ 2024-01-02 14:51 UTC (permalink / raw) To: Ray Andrews; +Cc: zsh-users [-- Attachment #1: Type: text/plain, Size: 1414 bytes --] On Sun, Dec 31, 2023 at 5:07 PM Ray Andrews <rayandrews@eastlink.ca> wrote: > > On 2023-12-31 13:44, Bart Schaefer wrote: > > On Sun, Dec 31, 2023 at 7:53 AM Ray Andrews <rayandrews@eastlink.ca> > wrote: > >> So that's multi-pass parsing. > > Again, no. It's only parsed once > > I don't have the background to understand it and it's probably not worth > your time to make it clear. > It's not parsed multiple times, but it _is parsed fully before anything runs. _Parsing_ is just reading the code and determining what it means; actually _running_ it comes later. Zsh parses the whole expression before it executes any part of it – it doesn't just stop and execute the first thing that looks like a command. So it has already read the `always` block and knows it's there before it executes the code to the left of it. And the presence of the `always` doesn't affect parsing; the code on both sides works the same way it would without the `always`. The only thing it does is tell Zsh that, as long as the code on the left doesn't exit the shell entirely, it doesn't matter what happens there; the code on the right needs to get executed next. Even if the left code interrupts the normal control flow with a `return` or `break` or `continue`, the code on the right will get executed before the next thing, wherever that next thing is. -- Mark J. Reed <markjreed@gmail.com> [-- Attachment #2: Type: text/html, Size: 2012 bytes --] ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2024-01-02 14:51 ` Mark J. Reed @ 2024-01-02 17:01 ` Ray Andrews 0 siblings, 0 replies; 25+ messages in thread From: Ray Andrews @ 2024-01-02 17:01 UTC (permalink / raw) To: zsh-users [-- Attachment #1: Type: text/plain, Size: 2399 bytes --] On 2024-01-02 06:51, Mark J. Reed wrote: > > It's not parsed multiple times, but it _is parsed fully before > anything runs. _Parsing_ is just reading the code and determining > what it means; actually _running_ it comes later. Zsh parses the whole > expression before it executes any part of it – it doesn't just stop > and execute the first thing that looks like a command. So it has > already read the `always` block and knows it's there before it > executes the code to the left of it. That's what I meant. Right tho -- it's handled in two passes but one is called parsing, the second is called running. But the parse sets a memo that zmv will not be permitted to crash the function at run time. > > And the presence of the `always` doesn't affect parsing; the code on > both sides works the same way it would without the `always`. The only > thing it does is tell Zsh that, as long as the code on the left > doesn't exit the shell entirely, it doesn't matter what happens there; > the code on the right needs to get executed next. Even if the left > code interrupts the normal control flow with a `return` or `break` or > `continue`, the code on the right will get executed before the next > thing, wherever that next thing is. This appears to be established practice, but I must say, seeing it for the first time, it sure looks bizarre. It looks like some hideous hack to handle a code flow discontinuity that might have had a more straightforward handling: zmv ... ... if [ $? -eq 0 ]; then echo "Files found, files moved boss."; fi if [ $? -eq 1 ]; then echo "Ooops, no files found, but no problemo, keep calm and carry on."; fi or: if [ $? -eq 1 ]; then echo "Ooops, no files found so this function won't have any more work to do so just return."; return; fi or: if [ $? -eq 1 ]; then echo "WARNING!, no files found!! This is a major problem so crash the entire shell."; exit; fi ... You guys know best however. But everything I've learned prior to this issue would have me expect that if zmv can't do anything it simply returns and code flow continues. I wonder why it can't be just that simple. It could very well be that other zsh functions have this 'crash the calling function' property but it's the first time I've seen it. (and zmv is soooo wonderful!) [-- Attachment #2: Type: text/html, Size: 3659 bytes --] ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2023-12-30 20:38 ` Bart Schaefer 2023-12-30 21:02 ` Ray Andrews 2023-12-30 21:15 ` Mikael Magnusson @ 2024-01-02 11:50 ` Peter Stephenson 2024-01-02 17:08 ` Ray Andrews 2 siblings, 1 reply; 25+ messages in thread From: Peter Stephenson @ 2024-01-02 11:50 UTC (permalink / raw) To: Zsh Users > On 30/12/2023 20:38 GMT Bart Schaefer <schaefer@brasslantern.com> wrote: > Arguably zmv could use null_glob. Thoughts from -workers? I guess the right thing to do regardless of option is that it should fail gracefully just by returning status 1 --- so not propagating the error to the caller. If the effect of NULL_GLOB is we loop over nothing, that presumably gives us status 0? I'd say we should probably detect that as a (return status) failure for minimum surprises. pws ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2024-01-02 11:50 ` Peter Stephenson @ 2024-01-02 17:08 ` Ray Andrews 2024-01-02 17:47 ` Mark J. Reed 2024-01-02 17:48 ` Peter Stephenson 0 siblings, 2 replies; 25+ messages in thread From: Ray Andrews @ 2024-01-02 17:08 UTC (permalink / raw) To: zsh-users On 2024-01-02 03:50, Peter Stephenson wrote: >> On 30/12/2023 20:38 GMT Bart Schaefer <schaefer@brasslantern.com> wrote: >> Arguably zmv could use null_glob. Thoughts from -workers? > I guess the right thing to do regardless of option is that it should > fail gracefully just by returning status 1 --- so not propagating the > error to the caller. That's just was I was trying to say :-) There's a lot to be said for graceful failure. If one wishes to crash the calling function one should of course have that option, but why make it the default? There's a sorta 'invisible return' there and the 'always' thing counteracts it, but it all seems so unnecessary. No invisible code, please. BTW, just to whine, 'always' is sure hard to find in the help. One obviously can't just search for that word, and it doesn't show up in the list of reserved words neither. ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2024-01-02 17:08 ` Ray Andrews @ 2024-01-02 17:47 ` Mark J. Reed 2024-01-02 18:16 ` Ray Andrews 2024-01-02 17:48 ` Peter Stephenson 1 sibling, 1 reply; 25+ messages in thread From: Mark J. Reed @ 2024-01-02 17:47 UTC (permalink / raw) To: Ray Andrews; +Cc: zsh-users [-- Attachment #1: Type: text/plain, Size: 2690 bytes --] On Tue, Jan 2, 2024 at 12:09 PM Ray Andrews <rayandrews@eastlink.ca> wrote: > BTW, just to whine, 'always' is sure hard to find in the help. One > obviously can't just search for that word, and it doesn't show up in the > list of reserved words neither. > Shell Grammar, under Complex Commands. (A bit of jargon, really; a command line to execute a program once is a "simple" command; anything that combines simple commands together – a loop or conditional or whatever –is a "complex" command): https://zsh.sourceforge.io/Doc/Release/Shell-Grammar.html#Complex-Commands > That's what I meant. Right tho -- it's handled in two passes but one is called parsing, the second is called running. But the parse sets a memo that zmv will not be permitted to crash the function at run time. Well, no. It runs the code on the left, and then when it finishes it runs the code on the right. That's the extent of the `always` functionality. What makes it special is that the code on the right runs even if the code on the left blows up somehow. It's similar to a feature called the "finally block" in other languages, where it's usually attached to the exception-handling mechanism; something like `try { start here } catch (ErrorType) { do this if the previous block failed this particular way } finally { run this no matter what }`. Because of that similarity, the code on the left of the `always` is called the "try block" or "try list", even though zsh doesn't use the keyword "try". The code on the right is called the "always block" or "always list". So, the point of `always` is to run the always block no matter what, even if the try block blows up. But now we have an information problem: the main point of the exercise was to run the code in the try block, and presumably the caller would like to know whether it blew up or not. But how does that information get back to them? You can't just go by exit code (`$?`/`$status`), because by the time control gets back to the caller, that will reflect the result of the always block, not the try block. Zsh uses a variable called TRY_BLOCK_ERROR (which should be read as "an error that occurred in the 'try' block" rather than some version of "try to block the error" :)) to remember whether the try block errored out or not. If it did, that variable will be 1, and after the always block executes, zsh will go back to handling the error (i.e. potentially blowing up the world). If you want the world not to be blown up, you can set TRY_BLOCK_ERROR to 0, and then zsh will forget that anything went wrong and continue about its business. -- Mark J. Reed <markjreed@gmail.com> [-- Attachment #2: Type: text/html, Size: 3543 bytes --] ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2024-01-02 17:47 ` Mark J. Reed @ 2024-01-02 18:16 ` Ray Andrews 2024-01-02 20:24 ` Bart Schaefer 0 siblings, 1 reply; 25+ messages in thread From: Ray Andrews @ 2024-01-02 18:16 UTC (permalink / raw) To: zsh-users [-- Attachment #1: Type: text/plain, Size: 1874 bytes --] On 2024-01-02 09:47, Mark J. Reed wrote: > > > https://zsh.sourceforge.io/Doc/Release/Shell-Grammar.html#Complex-Commands > Thanks. Man, I wish there were more examples :( > Well, no. It runs the code on the left, and then when it finishes it > runs the code on the right. That's the extent of the `always` > functionality. What makes it special is that the code on the right > runs even if the code on the left blows up somehow. Yeah, I think I get it. But the 'memo' has to be there -- zmv must ... or maybe not ... when zmv returns, if the 'always' keyword (sorta) it there, then it knows to not crash but to execute the following code. So, yeah, it could be seen as 'linear'. > > It's similar to a feature called the "finally block" in other > languages, where it's usually attached to the exception-handling > mechanism; something like `try { start here } catch (ErrorType) { do > this if the previous block failed this particular way } finally { run > this no matter what }`. Because of that similarity, the code on the > left of the `always` is called the "try block" or "try list", even > though zsh doesn't use the keyword "try". The code on the right is > called the "always block" or "always list". That's interesting. So this isn't some desperate hack -- that kind of functionality is standard to some degree. Ok, good to know. But as I said, it sure looks strange the first time you see it. Reading a bit more, I see it's got other uses than preventing meltdowns. Cool. > > If you want the world not to be blown up, you can set TRY_BLOCK_ERROR > to 0, and then zsh will forget that anything went wrong and continue > about its business. I myself hardly ever want Ragnarok, so next time I run into this sort of issue, I've got that to defuse it. Thanks Mark. Hey, I wonder if that could be an option? NO_MELTDOWNS=on [-- Attachment #2: Type: text/html, Size: 3607 bytes --] ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2024-01-02 18:16 ` Ray Andrews @ 2024-01-02 20:24 ` Bart Schaefer 2024-01-02 21:32 ` Ray Andrews 0 siblings, 1 reply; 25+ messages in thread From: Bart Schaefer @ 2024-01-02 20:24 UTC (permalink / raw) To: Ray Andrews; +Cc: zsh-users On Tue, Jan 2, 2024 at 10:16 AM Ray Andrews <rayandrews@eastlink.ca> wrote: > > Yeah, I think I get it. But the 'memo' has to be there -- zmv must ... or maybe not ... when zmv returns, if the 'always' keyword (sorta) it there, then it knows to not crash but to execute the following code. So, yeah, it could be seen as 'linear'. To avoid giving other readers a wrong conclusion ... There are a number of failure cases defined by e.g. the POSIX specification, or in some cases by longstanding zsh practice, that are considered fatal errors. When one of those errors is encountered, whatever it is that the shell is doing, simply stops. In a script, this exits the entire script; in an interactive shell, it returns to the top-level prompt. In this specific example, there is no "zmv returns": it ends, full stop, no return value, nothing back to the caller. This is not a "crash", it's a well-defined exit condition. (Whether it SHOULD be that condition specifically in zmv, is the topic elsewhere in this thread.) For a C equivalence, it's similar to abort() having been called. The "always" construct does nothing to the behavior of e.g. zmv, and does not "memo" anything. It allows calling code to intercept the well-defined exit state and clean up. (A "crash" would be an unrecoverable condition in which not even "always" would be possible.) If the calling code does not intercept the exit state, then the calling code also stops, and so on up the chain until (in an interactive shell) the top-level interpreter implicitly sets a non-zero status and prompts again. ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2024-01-02 20:24 ` Bart Schaefer @ 2024-01-02 21:32 ` Ray Andrews 0 siblings, 0 replies; 25+ messages in thread From: Ray Andrews @ 2024-01-02 21:32 UTC (permalink / raw) To: zsh-users [-- Attachment #1: Type: text/plain, Size: 1133 bytes --] On 2024-01-02 12:24, Bart Schaefer wrote: > When one of those errors is encountered, > whatever it is that the shell is doing, simply stops. In a script, > this exits the entire script; in an interactive shell, it returns to > the top-level prompt. In this specific example, there is no "zmv > returns": it ends, full stop, no return value, nothing back to the > caller. That's clear, but I had no idea that was the case. Nothing back? Sounds inadvisable but ... > This is not a "crash", it's a well-defined exit condition. Right, I'm corrected. Yes, 'crash' is a sloppy choice of words. I don't doubt zsh 'knows what it's doing' but it has the look of what Elon calls a rapid unscheduled disassembly. > The "always" construct does nothing to the behavior of e.g. zmv, and > does not "memo" anything. It allows calling code to intercept the > well-defined exit state and clean up. I think I'd have to see the actual nuts and bolts of it to really understand. It's not important. I learned quite a bit. Oh, when the devs have a final cut of the new zmv, if it's posted, I'll paste it over my copy here. [-- Attachment #2: Type: text/html, Size: 2052 bytes --] ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2024-01-02 17:08 ` Ray Andrews 2024-01-02 17:47 ` Mark J. Reed @ 2024-01-02 17:48 ` Peter Stephenson 2024-01-02 19:07 ` Bart Schaefer 1 sibling, 1 reply; 25+ messages in thread From: Peter Stephenson @ 2024-01-02 17:48 UTC (permalink / raw) To: zsh-users > On 02/01/2024 17:08 GMT Ray Andrews <rayandrews@eastlink.ca> wrote: > > > On 2024-01-02 03:50, Peter Stephenson wrote: > >> On 30/12/2023 20:38 GMT Bart Schaefer <schaefer@brasslantern.com> wrote: > >> Arguably zmv could use null_glob. Thoughts from -workers? > > I guess the right thing to do regardless of option is that it should > > fail gracefully just by returning status 1 --- so not propagating the > > error to the caller. > > That's just was I was trying to say :-) So how about this... pws diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index f43ac2257..182fc5f0a 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -4667,11 +4667,12 @@ renames `tt(foo.lis)' to `tt(foo.txt)', `tt(my.old.stuff.lis)' to `tt(my.old.stuff.txt)', and so on. The pattern is always treated as an tt(EXTENDED_GLOB) pattern. Any file -whose name is not changed by the substitution is simply ignored. Any -error (a substitution resulted in an empty string, two substitutions gave -the same result, the destination was an existing regular file and tt(-f) -was not given) causes the entire function to abort without doing -anything. +whose name is not changed by the substitution is simply ignored; if no +files are matched by the pattern, the function silently returns status +1. Any error (a substitution resulted in an empty string, two +substitutions gave the same result, the destination was an existing +regular file and tt(-f) was not given) causes the entire function to +abort without doing anything. In addition to pattern replacement, the variable tt($f) can be referred to in the second (replacement) argument. This makes it possible to diff --git a/Functions/Misc/zmv b/Functions/Misc/zmv index 269fe5ba5..177428f08 100644 --- a/Functions/Misc/zmv +++ b/Functions/Misc/zmv @@ -236,7 +236,11 @@ if [[ $pat = (#b)(*)\((\*\*##/)\)(*) ]]; then else fpat=$pat fi -files=(${~fpat}) +files=(${~fpat}(#qN)) + +if (( ${#files} == 0 )); then + return 1 +fi [[ -n $hasglobqual ]] && pat=$opat ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2024-01-02 17:48 ` Peter Stephenson @ 2024-01-02 19:07 ` Bart Schaefer 2024-01-02 19:52 ` Ray Andrews 2024-01-03 9:55 ` Peter Stephenson 0 siblings, 2 replies; 25+ messages in thread From: Bart Schaefer @ 2024-01-02 19:07 UTC (permalink / raw) To: Peter Stephenson; +Cc: zsh-users On Tue, Jan 2, 2024 at 9:48 AM Peter Stephenson <p.w.stephenson@ntlworld.com> wrote: > > So how about this... That's what my patch in users/29387 does, except mine emits an error message similar to all the other zmv error messages for pattern failures, rather than silently returning 1. ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2024-01-02 19:07 ` Bart Schaefer @ 2024-01-02 19:52 ` Ray Andrews 2024-01-03 9:55 ` Peter Stephenson 1 sibling, 0 replies; 25+ messages in thread From: Ray Andrews @ 2024-01-02 19:52 UTC (permalink / raw) To: zsh-users On 2024-01-02 11:07, Bart Schaefer wrote: > On Tue, Jan 2, 2024 at 9:48 AM Peter Stephenson > <p.w.stephenson@ntlworld.com> wrote: >> So how about this... > That's what my patch in users/29387 does, except mine emits an error > message similar to all the other zmv error messages for pattern > failures, rather than silently returning 1. Messages are always friendly especially since one can redirect them to null if one doesn't want to see them. Or just have a 'silent' option/switch. > ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2024-01-02 19:07 ` Bart Schaefer 2024-01-02 19:52 ` Ray Andrews @ 2024-01-03 9:55 ` Peter Stephenson 2024-01-03 15:46 ` Ray Andrews 1 sibling, 1 reply; 25+ messages in thread From: Peter Stephenson @ 2024-01-03 9:55 UTC (permalink / raw) To: zsh-users > On 02/01/2024 19:07 GMT Bart Schaefer <schaefer@brasslantern.com> wrote: > On Tue, Jan 2, 2024 at 9:48 AM Peter Stephenson > <p.w.stephenson@ntlworld.com> wrote: > > > > So how about this... > > That's what my patch in users/29387 does, except mine emits an error > message similar to all the other zmv error messages for pattern > failures, rather than silently returning 1. Sounds like that ought to be fine. pws ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2024-01-03 9:55 ` Peter Stephenson @ 2024-01-03 15:46 ` Ray Andrews 2024-01-03 19:01 ` Bart Schaefer 0 siblings, 1 reply; 25+ messages in thread From: Ray Andrews @ 2024-01-03 15:46 UTC (permalink / raw) To: zsh-users [-- Attachment #1: Type: text/plain, Size: 111 bytes --] On 2024-01-03 01:55, Peter Stephenson wrote: > Sounds like that ought to be fine. May I have a copy gentlemen? [-- Attachment #2: Type: text/html, Size: 551 bytes --] ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2024-01-03 15:46 ` Ray Andrews @ 2024-01-03 19:01 ` Bart Schaefer 0 siblings, 0 replies; 25+ messages in thread From: Bart Schaefer @ 2024-01-03 19:01 UTC (permalink / raw) To: Ray Andrews; +Cc: zsh-users [-- Attachment #1: Type: text/plain, Size: 97 bytes --] Updated patch including doc edit will be sent to zsh-workers. Here's a copy of the edited file. [-- Attachment #2: zmv --] [-- Type: application/octet-stream, Size: 11545 bytes --] # function zmv { # zmv, zcp, zln: # # This is a multiple move based on zsh pattern matching. To get the full # power of it, you need a postgraduate degree in zsh. However, simple # tasks work OK, so if that's all you need, here are some basic examples: # zmv '(*).txt' '$1.lis' # Rename foo.txt to foo.lis, etc. The parenthesis is the thing that # gets replaced by the $1 (not the `*', as happens in mmv, and note the # `$', not `=', so that you need to quote both words). # zmv '(**/)(*).txt '$1$2.lis' # The same, but scanning through subdirectories. The $1 becomes the full # path. Note that you need to write it like this; you can't get away with # '(**/*).txt'. # zmv -w '**/*.txt' '$1$2.lis' # noglob zmv -W **/*.txt **/*.lis # These are the lazy version of the one above; with -w, zsh inserts the # parentheses for you in the search pattern, and with -W it also inserts # the numbered variables for you in the replacement pattern. The catch # in the first version is that you don't need the / in the replacement # pattern. (It's not really a catch, since $1 can be empty.) Note that # -W actually inserts ${1}, ${2}, etc., so it works even if you put a # number after a wildcard (such as zmv -W '*1.txt' '*2.txt'). # zmv -C '**/(*).txt' ~/save/'$1'.lis # Copy, instead of move, all .txt files in subdirectories to .lis files # in the single directory `~/save'. Note that the ~ was not quoted. # You can test things safely by using the `-n' (no, not now) option. # Clashes, where multiple files are renamed or copied to the same one, are # picked up. # # Here's a more detailed description. # # Use zsh pattern matching to move, copy or link files, depending on # the last two characters of the function name. The general syntax is # zmv '<inpat>' '<outstring>' # <inpat> is a globbing pattern, so it should be quoted to prevent it from # immediate expansion, while <outstring> is a string that will be # re-evaluated and hence may contain parameter substitutions, which should # also be quoted. Each set of parentheses in <inpat> (apart from those # around glob qualifiers, if you use the -Q option, and globbing flags) may # be referred to by a positional parameter in <outstring>, i.e. the first # (...) matched is given by $1, and so on. For example, # zmv '([a-z])(*).txt' '${(C)1}$2.txt' # renames algernon.txt to Algernon.txt, boris.txt to Boris.txt and so on. # The original file matched can be referred to as $f in the second # argument; accidental or deliberate use of other parameters is at owner's # risk and is not covered by the (non-existent) guarantee. # # As usual in zsh, /'s don't work inside parentheses. There is a special # case for (**/) and (***/): these have the expected effect that the # entire relevant path will be substituted by the appropriate positional # parameter. # # There is a shortcut avoiding the use of parenthesis with the option -w # (with wildcards), which picks out any expressions `*', `?', `<range>' # (<->, <1-10>, etc.), `[...]', possibly followed by `#'s, `**/', `***/', and # automatically parenthesises them. (You should quote any ['s or ]'s which # appear inside [...] and which do not come from ranges of the form # `[:alpha:]'.) So for example, in # zmv -w '[[:upper:]]*' '${(L)1}$2' # the $1 refers to the expression `[[:upper:]]' and the $2 refers to # `*'. Thus this finds any file with an upper case first character and # renames it to one with a lowercase first character. Note that any # existing parentheses are active, too, so you must count accordingly. # Furthermore, an expression like '(?)' will be rewritten as '((?))' --- in # other words, parenthesising of wildcards is independent of any existing # parentheses. # # Any file whose name is not changed by the substitution is simply ignored. # Any error --- a substitution resulted in an empty string, two # substitutions gave the same result, the destination was an existing # regular file and -f was not given --- causes the entire function to abort # without doing anything. # # Options: # -f force overwriting of destination files. Not currently passed # down to the mv/cp/ln command due to vagaries of implementations # (but you can use -o-f to do that). # -i interactive: show each line to be executed and ask the user whether # to execute it. Y or y will execute it, anything else will skip it. # Note that you just need to type one character. # -n no execution: print what would happen, but don't do it. # -q Turn bare glob qualifiers off: now assumed by default, so this # has no effect. # -Q Force bare glob qualifiers on. Don't turn this on unless you are # actually using glob qualifiers in a pattern (see below). # -s symbolic, passed down to ln; only works with zln or z?? -L. # -v verbose: print line as it's being executed. # -o <optstring> # <optstring> will be split into words and passed down verbatim # to the cp, ln or mv called to perform the work. It will probably # begin with a `-'. # -p <program> # Call <program> instead of cp, ln or mv. Whatever it does, it should # at least understand the form '<program> -- <oldname> <newname>', # where <oldname> and <newname> are filenames generated. <program> # will be split into words. # -P <program> # As -p, but the program doesn't understand the "--" convention. # In this case the file names must already be sane. # -w Pick out wildcard parts of the pattern, as described above, and # implicitly add parentheses for referring to them. # -W Just like -w, with the addition of turning wildcards in the # replacement pattern into sequential ${1} .. ${N} references. # -C # -L # -M Force cp, ln or mv, respectively, regardless of the name of the # function. # # Bugs: # Parenthesised expressions can be confused with glob qualifiers, for # example a trailing '(*)' would be treated as a glob qualifier in # ordinary globbing. This has proved so annoying that glob qualifiers # are now turned off by default. To force the use of glob qualifiers, # give the flag -Q. # # The pattern is always treated as an extendedglob pattern. This # can also be interpreted as a feature. # # Unbugs: # You don't need braces around the 1 in expressions like '$1t' as # non-positional parameters may not start with a number, although # paranoiacs like the author will probably put them there anyway. emulate -RL zsh setopt extendedglob local f g args match mbegin mend files action myname tmpf opt exec local opt_f opt_i opt_n opt_q opt_Q opt_s opt_M opt_C opt_L local opt_o opt_p opt_P opt_v opt_w opt_W MATCH MBEGIN MEND local pat repl errstr fpat hasglobqual opat typeset -A from to integer stat local dashes=-- myname=${(%):-%N} while getopts ":o:p:P:MCLfinqQsvwW" opt; do if [[ $opt = "?" ]]; then print -r -- "$myname: unrecognized option: -$OPTARG" >&2 return 1 fi eval "opt_$opt=\${OPTARG:--\$opt}" done (( OPTIND > 1 )) && shift $(( OPTIND - 1 )) [[ -z $opt_Q ]] && setopt nobareglobqual [[ -n $opt_M ]] && action=mv [[ -n $opt_C ]] && action=cp [[ -n $opt_L ]] && action=ln [[ -n $opt_p ]] && action=$opt_p [[ -n $opt_P ]] && action=$opt_P dashes= if [[ -z $action ]]; then action=$myname[-2,-1] if [[ $action != (cp|mv|ln) ]]; then print -r "$myname: action $action not recognised: must be cp, mv or ln." >&2 return 1 fi fi if (( $# != 2 )); then print -P "Usage: %N [OPTIONS] oldpattern newpattern where oldpattern contains parenthesis surrounding patterns which will be replaced in turn by \$1, \$2, ... in newpattern. For example, %N '(*).lis' '\$1.txt' renames 'foo.lis' to 'foo.txt', 'my.old.stuff.lis' to 'my.old.stuff.txt', and so on. Something simpler (for basic commands) is the -W option: %N -W '*.lis' '*.txt' This does the same thing as the first command, but with automatic conversion of the wildcards into the appropriate syntax. If you combine this with noglob, you don't even need to quote the arguments. For example, alias mmv='noglob zmv -W' mmv *.c.orig orig/*.c" >&2 return 1 fi pat=$1 repl=$2 shift 2 if [[ -n $opt_s && $action != ln ]]; then print -r -- "$myname: invalid option: -s" >&2 return 1 fi if [[ -n $opt_w || -n $opt_W ]]; then # Parenthesise all wildcards. local tmp find integer cnt=0 # Well, this seems to work. # The tricky bit is getting all forms of [...] correct, but as long # as we require inactive bits to be backslashed its not so bad. find='(#m)((\*\*##/|[*?]|<[0-9]#-[0-9]#>|\[(^|)(\]|)(\[:[a-z]##:\]|\\?|[^\]])##\])\##|?\###)' tmp="${pat//${~find}/$[++cnt]}" if [[ $cnt = 0 ]]; then print -r -- "$myname: warning: no wildcards were found in search pattern" >&2 else pat="${pat//${~find}/($MATCH)}" fi if [[ -n $opt_W ]]; then # Turn wildcards into ${1} .. ${N} references. local open='${' close='}' integer N=0 repl="${repl//${~find}/$open$[++N]$close}" if [[ $N != $cnt ]]; then print -P "%N: error: number of wildcards in each pattern must match" >&2 return 1 fi if [[ $N = 0 ]]; then print -P "%N: warning: no wildcards were found in replacement pattern" >&2 fi fi fi if [[ -n $opt_Q && $pat = (#b)(*)\([^\)\|\~]##\) ]]; then hasglobqual=q # strip off qualifiers for use as ordinary pattern opat=$match[1] fi if [[ $pat = (#b)(*)\((\*\*##/)\)(*) ]]; then fpat="$match[1]$match[2]$match[3]" # Now make sure we do depth-first searching. # This is so that the names of any files are altered before the # names of the directories they are in. if [[ -n $opt_Q && -n $hasglobqual ]]; then fpat[-1]="odon)" else setopt bareglobqual fpat="${fpat}(odon)" fi else fpat=$pat fi [[ -n $hasglobqual ]] && pat=$opat errs=() () { # (#qN) breaks bareglobqual -Q option, so: setopt localoptions nullglob files=(${~fpat}) } (( ${#files} )) || errs=( "no files matched \`$fpat'" ) for f in $files; do if [[ $pat = (#b)(*)\(\*\*##/\)(*) ]]; then # This looks like a recursive glob. This isn't good enough, # because we should really enforce that $match[1] and $match[2] # don't match slashes unless they were explicitly given. But # it's a start. It's fine for the classic case where (**/) is # at the start of the pattern. pat="$match[1](*/|)$match[2]" fi [[ -e $f && $f = (#b)${~pat} ]] || continue set -- "$match[@]" { { g=${(Xe)repl} } 2> /dev/null } always { if (( TRY_BLOCK_ERROR )); then print -r -- "$myname: syntax error in replacement" >&2 return 1 fi } if [[ -z $g ]]; then errs+=("\`$f' expanded to an empty string") elif [[ $f = $g ]]; then # don't cause error: more useful just to skip # errs=($errs "$f not altered by substitution") [[ -n $opt_v ]] && print -r -- "$f not altered, ignored" continue elif [[ -n $from[$g] && ! -d $g ]]; then errs+=("$f and $from[$g] both map to $g") elif [[ -f $g && -z $opt_f && ! ($f -ef $g && $action = mv) ]]; then errs+=("file exists: $g") fi from[$g]=$f to[$f]=$g done if (( $#errs )); then print -r -- "$myname: error(s) in substitution:" >&2 print -lr -- $errs >&2 return 1 fi for f in $files; do [[ -z $to[$f] ]] && continue exec=(${=action} ${=opt_o} $opt_s $dashes $f $to[$f]) [[ -n $opt_i$opt_n$opt_v ]] && print -r -- ${(q-)exec} if [[ -n $opt_i ]]; then read -q 'opt?Execute? ' || continue fi if [[ -z $opt_n ]]; then $exec || stat=1 fi done return $stat # } ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: zmv exits from function 2023-12-30 17:42 zmv exits from function Ray Andrews 2023-12-30 20:38 ` Bart Schaefer @ 2023-12-30 22:34 ` Bart Schaefer 1 sibling, 0 replies; 25+ messages in thread From: Bart Schaefer @ 2023-12-30 22:34 UTC (permalink / raw) To: Ray Andrews; +Cc: Zsh Users On Sat, Dec 30, 2023 at 9:43 AM Ray Andrews <rayandrews@eastlink.ca> wrote: > > I'm using zmv to rename files in a directory tree recursively via a > 'for' loop which visits each subdirectory. Incidentally, unless your "f" function is doing something non-obvious, zmv can recursively search directories all by itself. Try (without your "for" wrapper and all the "cd"-ing): zmv -n '(*/)#(*).(SNT|MES)' '$1$2.eml' If that looks like it would do the right thing, remove the "-n". ^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2024-01-03 19:02 UTC | newest] Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2023-12-30 17:42 zmv exits from function Ray Andrews 2023-12-30 20:38 ` Bart Schaefer 2023-12-30 21:02 ` Ray Andrews 2023-12-30 22:21 ` Bart Schaefer 2023-12-30 21:15 ` Mikael Magnusson 2023-12-31 3:43 ` Ray Andrews 2023-12-31 3:58 ` Bart Schaefer 2023-12-31 15:53 ` Ray Andrews 2023-12-31 21:44 ` Bart Schaefer 2023-12-31 22:06 ` Ray Andrews 2024-01-02 14:51 ` Mark J. Reed 2024-01-02 17:01 ` Ray Andrews 2024-01-02 11:50 ` Peter Stephenson 2024-01-02 17:08 ` Ray Andrews 2024-01-02 17:47 ` Mark J. Reed 2024-01-02 18:16 ` Ray Andrews 2024-01-02 20:24 ` Bart Schaefer 2024-01-02 21:32 ` Ray Andrews 2024-01-02 17:48 ` Peter Stephenson 2024-01-02 19:07 ` Bart Schaefer 2024-01-02 19:52 ` Ray Andrews 2024-01-03 9:55 ` Peter Stephenson 2024-01-03 15:46 ` Ray Andrews 2024-01-03 19:01 ` Bart Schaefer 2023-12-30 22:34 ` Bart Schaefer
Code repositories for project(s) associated with this public inbox https://git.vuxu.org/mirror/zsh/ This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).