Hello, I've spotted a few cases in the doc of things like "ls *" instead of "ls -d -- *". While that's something one could do interactively when they know how the files are named and their nature, that's bad practice in scripts. As a reference manual, IMO, the zsh documentation should show "the right way". - always use "--" when using globs or wherever the arguments are not known in advance and can't be guaranteed not to start with - or + - never use ls without -d unless you want it to list the *contents* of directory arguments. (I've not made a thorough scan, there might be more I missed). I include some related changes spotted on the way: - mention echo in the list of builtins that don't support -- - note print -C1 as the canonical way to print a list of arguments one per line as I've changed one example to use that. diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 415bce613..6c1c4f41a 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -39,11 +39,11 @@ be familiar to most command line users. Typically, options are single letters preceded by a hyphen (tt(-)). Options that take an argument accept it either immediately following the -option letter or after white space, for example `tt(print -C3 *)' or -`tt(print -C 3 *)' are equivalent. Arguments to options are not the +option letter or after white space, for example `tt(print -C3 {1..9})' or +`tt(print -C 3 {1..9})' are equivalent. Arguments to options are not the same as arguments to the command; the documentation indicates which is which. Options that do not take an argument may be combined in a single -word, for example `tt(print -ca *)' and `tt(print -c -a *)' are +word, for example `tt(print -rca -- *)' and `tt(print -r -c -a -- *)' are equivalent. Some shell builtin commands also take options that begin with `tt(+)' @@ -54,14 +54,14 @@ Options (together with their individual arguments, if any) must appear in a group before any non-option arguments; once the first non-option argument has been found, option processing is terminated. -All builtin commands other than precommand modifiers, even those that -have no options, can be given the argument `tt(-)tt(-)' to terminate option -processing. This indicates that the following words are non-option -arguments, but is otherwise ignored. This is useful in cases where -arguments to the command may begin with `tt(-)'. For historical -reasons, most builtin commands also recognize a single `tt(-)' in a -separate word for this purpose; note that this is less standard and -use of `tt(-)tt(-)' is recommended. +All builtin commands other than `tt(echo)' and precommand modifiers, +even those that have no options, can be given the argument `tt(-)tt(-)' +to terminate option processing. This indicates that the following words +are non-option arguments, but is otherwise ignored. This is useful in +cases where arguments to the command may begin with `tt(-)'. For +historical reasons, most builtin commands (including `tt(echo)') also +recognize a single `tt(-)' in a separate word for this purpose; note +that this is less standard and use of `tt(-)tt(-)' is recommended. startitem() prefix(-) @@ -114,9 +114,9 @@ var(text) is any non-empty string, it is replaced by the text a literal string, not a pattern. A trailing space in var(value) is not special in this case. For example, -example(alias -s ps=gv) +example(alias -s ps='gv --') -will cause the command `tt(*.ps)' to be expanded to `tt(gv *.ps)'. As +will cause the command `tt(*.ps)' to be expanded to `tt(gv -- *.ps)'. As alias expansion is carried out earlier than globbing, the `tt(*.ps)' will then be expanded. Suffix aliases constitute a different name space from other aliases (so in the above example it is still possible @@ -1258,7 +1258,11 @@ If given together with tt(-o) or tt(-O), sorting is performed case-independently. ) item(tt(-l))( -Print the arguments separated by newlines instead of spaces. +Print the arguments separated by newlines instead of spaces. Note: +if the list of arguments is empty, tt(print -l) as opposed to +tt(print -C1), still outputs one empty line; tt(print -rC1 -- +"$list[@]") is a more canonical way to print an arbitrary list of +arguments, one per line. ) item(tt(-m))( Take the first argument as a pattern (should be quoted), and remove @@ -1269,7 +1273,9 @@ item(tt(-n))( Do not add a newline to the output. ) item(tt(-N))( -Print the arguments separated and terminated by nulls. +Print the arguments separated and terminated by nulls. Again, +tt(print -rNC1 -- "$list[@]") is a canonical way to print an +arbitrary list as null-delimited records. ) item(tt(-o))( Print the arguments sorted in ascending order. diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index 558342711..743ac73dc 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -4422,10 +4422,11 @@ For example, to get a long tt(ls) listing of all plain files in the current directory or its subdirectories: example(autoload -U zargs -zargs -- **/*(.) -- ls -l) +zargs -- **/*(.) -- ls -ld --) Note that `tt(-)tt(-)' is used both to mark the end of the var(option) -list and to mark the end of the var(input) list, so it must appear twice +list and to mark the end of the var(input) list (and here also to mark +the end of the option list for tt(ls)), so it must appear twice whenever the var(input) list may be empty. If there is guaranteed to be at least one var(input) and the first var(input) does not begin with a `tt(-)', then the first `tt(-)tt(-)' may be omitted. @@ -4435,7 +4436,7 @@ tt(-e) option may be used to change the end-of-inputs marker. Note that this does em(not) change the end-of-options marker. For example, to use `tt(..)' as the marker: -example(zargs -e.. -- **/*(.) .. ls -l) +example(zargs -e.. -- **/*(.) .. ls -ld --) This is a good choice in that example because no plain file can be named `tt(..)', but the best end-marker depends on the circumstances. diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index d7147dbd7..6f4700dc4 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -400,7 +400,7 @@ backslashes. For example, the following piece of filename generation code with the tt(EXTENDED_GLOB) option: -example(print *.c+LPAR()#q:s/#%+LPAR()#b+RPAR()s+LPAR()*+RPAR().c/'S${match[1]}.C'/+RPAR()) +example(print -r -- *.c+LPAR()#q:s/#%+LPAR()#b+RPAR()s+LPAR()*+RPAR().c/'S${match[1]}.C'/+RPAR()) takes the expansion of tt(*.c) and applies the glob qualifiers in the tt(LPAR()#q)var(...)tt(RPAR()) expression, which consists of a substitution @@ -2492,11 +2492,11 @@ therefore matches files in the current directory as well as subdirectories. Thus: -example(ls (*/)#bar) +example(ls -ld -- (*/)#bar) or -example(ls **/bar) +example(ls -ld -- **/bar) does a recursive directory search for files named `tt(bar)' (potentially including the file `tt(bar)' in the current directory). This form does not @@ -2511,11 +2511,11 @@ they are treated as if both a tt(/) plus a further tt(*) are present. Hence: example(setopt GLOBSTARSHORT -ls **.c) +ls -ld -- **.c) is equivalent to -example(ls **/*.c) +example(ls -ld -- **/*.c) subsect(Glob Qualifiers) cindex(globbing, qualifiers) cindex(qualifiers, globbing) @@ -2707,7 +2707,7 @@ appropriate test. For example, example(nt+LPAR()RPAR() { [[ $REPLY -nt $NTREF ]] } NTREF=reffile -ls -l *(+nt)) +ls -ld -- *(+nt)) lists all files in the directory that have been modified more recently than tt(reffile). @@ -2898,36 +2898,36 @@ is performed, although note that the presence of the parentheses causes the entire expression to be subjected to any global pattern matching options such as tt(NULL_GLOB). Thus: -example(ls *(-/)) +example(ls -ld -- *(-/)) lists all directories and symbolic links that point to directories, and -example(ls *(-@)) +example(ls -ld -- *(-@)) lists all broken symbolic links, and -example(ls *(%W)) +example(ls -ld -- *(%W)) lists all world-writable device files in the current directory, and -example(ls *(W,X)) +example(ls -ld -- *(W,X)) lists all files in the current directory that are world-writable or world-executable, and -example(echo /tmp/foo*(u0^@:t)) +example(print -rC1 /tmp/foo*(u0^@:t)) outputs the basename of all root-owned files beginning with the string `tt(foo)' in tt(/tmp), ignoring symlinks, and -example(ls *.*~(lex|parse).[ch](^D^l1)) +example(ls -ld -- *.*~(lex|parse).[ch](^D^l1)) lists all files having a link count of one whose names contain a dot (but not those starting with a dot, since tt(GLOB_DOTS) is explicitly switched off) except for tt(lex.c), tt(lex.h), tt(parse.c) and tt(parse.h). -example(print b*.pro+LPAR()#q:s/pro/shmo/+RPAR()(#q.:s/builtin/shmiltin/)) +example(print -rC1 b*.pro+LPAR()#q:s/pro/shmo/+RPAR()(#q.:s/builtin/shmiltin/)) demonstrates how colon modifiers and other qualifiers may be chained together. The ordinary qualifier `tt(.)' is applied first, then the colon
Stephane Chazelas wrote on Tue, Dec 31, 2019 at 09:16:18 +0000: > Hello, I've spotted a few cases in the doc of things like "ls *" > instead of "ls -d -- *". > > While that's something one could do interactively when they know > how the files are named and their nature, that's bad practice in > scripts. As a reference manual, IMO, the zsh documentation > should show "the right way". +1 and thanks for the patch. Two small suggestions: > {+++ b/Doc/Zsh/builtins.yo+} > @@ -1258,7 +1258,11 @@ If given together with tt(-o) or tt(-O), sorting is performed > item(tt(-l))( > -Print the arguments separated by newlines instead of spaces. > +Print the arguments separated by newlines instead of spaces. Note: > +if the list of arguments is empty, tt(print -l) as opposed to > +tt(print -C1), still outputs one empty line; tt(print -rC1 -- > +"$list[@]") is a more canonical way to print an arbitrary list of > +arguments, one per line. Suggest not to mention the solution before the problem: +Note: if the list of arguments is empty, tt(print -l) will still output one +empty line. To print a possibly-empty list of arguments one per line, use +tt(print -C1), as in `tt(print -rC1 -- "$list[@]")'. > {+++ b/Doc/Zsh/contrib.yo+} > @@ -4422,10 +4422,11 @@ For example, to get a long tt(ls) listing of all plain files in the > current directory or its subdirectories: > > example(autoload -U zargs > zargs -- **/*(.) -- ls [--l)-] {+-ld --)+} > > Note that `tt(-)tt(-)' is used both to mark the end of the var(option) > list and to mark the end of the var(input) [-list,-] {+list (and here also to mark > the end of the option list for tt(ls)),+} so it must appear twice > whenever the var(input) list may be empty. If there is guaranteed to be > at least one var(input) and the first var(input) does not begin with a > `tt(-)', then the first `tt(-)tt(-)' may be omitted. I think the text could be a little hard to follow, since there are three «--» but it talks about "both" and then mentions the third in an afterthought. Would you prefer to change the incumbent text as well? You're not limited to adding parentheticals. Cheers, Daniel
2019-12-31 17:16:38 +0000, Daniel Shahaf: [...] > Suggest not to mention the solution before the problem: > > +Note: if the list of arguments is empty, tt(print -l) will still output one > +empty line. To print a possibly-empty list of arguments one per line, use > +tt(print -C1), as in `tt(print -rC1 -- "$list[@]")'. [...] Thanks for the feedback. Yes, that works for me. > > {+++ b/Doc/Zsh/contrib.yo+} > > @@ -4422,10 +4422,11 @@ For example, to get a long tt(ls) listing of all plain files in the > > current directory or its subdirectories: > > > > example(autoload -U zargs > > zargs -- **/*(.) -- ls [--l)-] {+-ld --)+} > > > > Note that `tt(-)tt(-)' is used both to mark the end of the var(option) > > list and to mark the end of the var(input) [-list,-] {+list (and here also to mark > > the end of the option list for tt(ls)),+} so it must appear twice > > whenever the var(input) list may be empty. If there is guaranteed to be > > at least one var(input) and the first var(input) does not begin with a > > `tt(-)', then the first `tt(-)tt(-)' may be omitted. > > I think the text could be a little hard to follow, since there are three > «--» but it talks about "both" and then mentions the third in an > afterthought. Would you prefer to change the incumbent text as well? > You're not limited to adding parentheticals. I see what you mean. How about something like: zargs -- **/*(.) -- ls -ld -- The first and third occurrences of -- are used to mark the end of options for zargs and ls respectively to guard against filenames starting with -, while the second is used to separate the list of files from the command to run. The first -- would also be needed if there was a chance the list might be empty as in: zargs -r -- ./*.back(#qN) -- rm -f -- Stephane
Stephane Chazelas wrote on Wed, Jan 01, 2020 at 12:00:09 +0000:
> 2019-12-31 17:16:38 +0000, Daniel Shahaf:
> [...]
> > > {+++ b/Doc/Zsh/contrib.yo+}
> > > @@ -4422,10 +4422,11 @@ For example, to get a long tt(ls) listing of all plain files in the
> > > current directory or its subdirectories:
> > >
> > > example(autoload -U zargs
> > > zargs -- **/*(.) -- ls [--l)-] {+-ld --)+}
> > >
> > > Note that `tt(-)tt(-)' is used both to mark the end of the var(option)
> > > list and to mark the end of the var(input) [-list,-] {+list (and here also to mark
> > > the end of the option list for tt(ls)),+} so it must appear twice
> > > whenever the var(input) list may be empty. If there is guaranteed to be
> > > at least one var(input) and the first var(input) does not begin with a
> > > `tt(-)', then the first `tt(-)tt(-)' may be omitted.
> >
> > I think the text could be a little hard to follow, since there are three
> > «--» but it talks about "both" and then mentions the third in an
> > afterthought. Would you prefer to change the incumbent text as well?
> > You're not limited to adding parentheticals.
>
> I see what you mean. How about something like:
>
> zargs -- **/*(.) -- ls -ld --
>
> The first and third occurrences of -- are used to mark the end
> of options for zargs and ls respectively to guard against
> filenames starting with -, while the second is used to separate
> the list of files from the command to run.
>
> The first -- would also be needed if there was a chance the list
> might be empty as in:
>
> zargs -r -- ./*.back(#qN) -- rm -f
Looks good, thanks. Please commit.
Further suggestions (nice-to-have's, not blockers):
- Clarify that the third -- isn't syntactically significant, and is just passed
to ls verbatim.
- Use an example with two --'s before an example with three --'s.
Cheers,
Daniel
2020-01-01 12:52:13 +0000, Daniel Shahaf: [...] > Looks good, thanks. Please commit. > > Further suggestions (nice-to-have's, not blockers): > > - Clarify that the third -- isn't syntactically significant, and is just passed > to ls verbatim. [...] Please find a new version below, with a small change that goes in that direction. Note that I'm not a commiter (I don't mind it staying that way), someone else would have to commit for me. diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 415bce613..ada69c99a 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -39,11 +39,11 @@ be familiar to most command line users. Typically, options are single letters preceded by a hyphen (tt(-)). Options that take an argument accept it either immediately following the -option letter or after white space, for example `tt(print -C3 *)' or -`tt(print -C 3 *)' are equivalent. Arguments to options are not the +option letter or after white space, for example `tt(print -C3 {1..9})' or +`tt(print -C 3 {1..9})' are equivalent. Arguments to options are not the same as arguments to the command; the documentation indicates which is which. Options that do not take an argument may be combined in a single -word, for example `tt(print -ca *)' and `tt(print -c -a *)' are +word, for example `tt(print -rca -- *)' and `tt(print -r -c -a -- *)' are equivalent. Some shell builtin commands also take options that begin with `tt(+)' @@ -54,14 +54,14 @@ Options (together with their individual arguments, if any) must appear in a group before any non-option arguments; once the first non-option argument has been found, option processing is terminated. -All builtin commands other than precommand modifiers, even those that -have no options, can be given the argument `tt(-)tt(-)' to terminate option -processing. This indicates that the following words are non-option -arguments, but is otherwise ignored. This is useful in cases where -arguments to the command may begin with `tt(-)'. For historical -reasons, most builtin commands also recognize a single `tt(-)' in a -separate word for this purpose; note that this is less standard and -use of `tt(-)tt(-)' is recommended. +All builtin commands other than `tt(echo)' and precommand modifiers, +even those that have no options, can be given the argument `tt(-)tt(-)' +to terminate option processing. This indicates that the following words +are non-option arguments, but is otherwise ignored. This is useful in +cases where arguments to the command may begin with `tt(-)'. For +historical reasons, most builtin commands (including `tt(echo)') also +recognize a single `tt(-)' in a separate word for this purpose; note +that this is less standard and use of `tt(-)tt(-)' is recommended. startitem() prefix(-) @@ -114,9 +114,9 @@ var(text) is any non-empty string, it is replaced by the text a literal string, not a pattern. A trailing space in var(value) is not special in this case. For example, -example(alias -s ps=gv) +example(alias -s ps='gv --') -will cause the command `tt(*.ps)' to be expanded to `tt(gv *.ps)'. As +will cause the command `tt(*.ps)' to be expanded to `tt(gv -- *.ps)'. As alias expansion is carried out earlier than globbing, the `tt(*.ps)' will then be expanded. Suffix aliases constitute a different name space from other aliases (so in the above example it is still possible @@ -1258,7 +1258,10 @@ If given together with tt(-o) or tt(-O), sorting is performed case-independently. ) item(tt(-l))( -Print the arguments separated by newlines instead of spaces. +Print the arguments separated by newlines instead of spaces. Note: if +the list of arguments is empty, tt(print -l) will still output one empty +line. To print a possibly-empty list of arguments one per line, use +tt(print -C1), as in `tt(print -rC1 -- "$list[@]")'. ) item(tt(-m))( Take the first argument as a pattern (should be quoted), and remove @@ -1269,7 +1272,9 @@ item(tt(-n))( Do not add a newline to the output. ) item(tt(-N))( -Print the arguments separated and terminated by nulls. +Print the arguments separated and terminated by nulls. Again, +tt(print -rNC1 -- "$list[@]") is a canonical way to print an +arbitrary list as null-delimited records. ) item(tt(-o))( Print the arguments sorted in ascending order. diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index 558342711..871c3e6f5 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -4418,24 +4421,28 @@ the elements from the tt(input) list in each run. If no var(command) is provided, then no var(arg) list may be provided, and in that event the default command is `tt(print)' with arguments `tt(-r -)tt(-)'. -For example, to get a long tt(ls) listing of all plain files in the -current directory or its subdirectories: +For example, to get a long tt(ls) listing of all non-hidden plain files +in the current directory or its subdirectories: example(autoload -U zargs -zargs -- **/*(.) -- ls -l) +zargs -- **/*(.) -- ls -ld --) + +The first and third occurrences of `tt(-)tt(-)' are used to mark the end +of options for tt(zargs) and tt(ls) respectively to guard against +filenames starting with `tt(-)', while the second is used to separate the +list of files from the command to run (`tt(ls -ld --)'). + +The first `tt(-)tt(-)' would also be needed if there was a chance the +list might be empty as in: -Note that `tt(-)tt(-)' is used both to mark the end of the var(option) -list and to mark the end of the var(input) list, so it must appear twice -whenever the var(input) list may be empty. If there is guaranteed to be -at least one var(input) and the first var(input) does not begin with a -`tt(-)', then the first `tt(-)tt(-)' may be omitted. +example(zargs -r -- ./*.back(#qN) -- rm -f) In the event that the string `tt(-)tt(-)' is or may be an var(input), the tt(-e) option may be used to change the end-of-inputs marker. Note that this does em(not) change the end-of-options marker. For example, to use `tt(..)' as the marker: -example(zargs -e.. -- **/*(.) .. ls -l) +example(zargs -e.. -- **/*(.) .. ls -ld --) This is a good choice in that example because no plain file can be named `tt(..)', but the best end-marker depends on the circumstances. diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index d7147dbd7..6f4700dc4 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -400,7 +400,7 @@ backslashes. For example, the following piece of filename generation code with the tt(EXTENDED_GLOB) option: -example(print *.c+LPAR()#q:s/#%+LPAR()#b+RPAR()s+LPAR()*+RPAR().c/'S${match[1]}.C'/+RPAR()) +example(print -r -- *.c+LPAR()#q:s/#%+LPAR()#b+RPAR()s+LPAR()*+RPAR().c/'S${match[1]}.C'/+RPAR()) takes the expansion of tt(*.c) and applies the glob qualifiers in the tt(LPAR()#q)var(...)tt(RPAR()) expression, which consists of a substitution @@ -2492,11 +2492,11 @@ therefore matches files in the current directory as well as subdirectories. Thus: -example(ls (*/)#bar) +example(ls -ld -- (*/)#bar) or -example(ls **/bar) +example(ls -ld -- **/bar) does a recursive directory search for files named `tt(bar)' (potentially including the file `tt(bar)' in the current directory). This form does not @@ -2511,11 +2511,11 @@ they are treated as if both a tt(/) plus a further tt(*) are present. Hence: example(setopt GLOBSTARSHORT -ls **.c) +ls -ld -- **.c) is equivalent to -example(ls **/*.c) +example(ls -ld -- **/*.c) subsect(Glob Qualifiers) cindex(globbing, qualifiers) cindex(qualifiers, globbing) @@ -2707,7 +2707,7 @@ appropriate test. For example, example(nt+LPAR()RPAR() { [[ $REPLY -nt $NTREF ]] } NTREF=reffile -ls -l *(+nt)) +ls -ld -- *(+nt)) lists all files in the directory that have been modified more recently than tt(reffile). @@ -2898,36 +2898,36 @@ is performed, although note that the presence of the parentheses causes the entire expression to be subjected to any global pattern matching options such as tt(NULL_GLOB). Thus: -example(ls *(-/)) +example(ls -ld -- *(-/)) lists all directories and symbolic links that point to directories, and -example(ls *(-@)) +example(ls -ld -- *(-@)) lists all broken symbolic links, and -example(ls *(%W)) +example(ls -ld -- *(%W)) lists all world-writable device files in the current directory, and -example(ls *(W,X)) +example(ls -ld -- *(W,X)) lists all files in the current directory that are world-writable or world-executable, and -example(echo /tmp/foo*(u0^@:t)) +example(print -rC1 /tmp/foo*(u0^@:t)) outputs the basename of all root-owned files beginning with the string `tt(foo)' in tt(/tmp), ignoring symlinks, and -example(ls *.*~(lex|parse).[ch](^D^l1)) +example(ls -ld -- *.*~(lex|parse).[ch](^D^l1)) lists all files having a link count of one whose names contain a dot (but not those starting with a dot, since tt(GLOB_DOTS) is explicitly switched off) except for tt(lex.c), tt(lex.h), tt(parse.c) and tt(parse.h). -example(print b*.pro+LPAR()#q:s/pro/shmo/+RPAR()(#q.:s/builtin/shmiltin/)) +example(print -rC1 b*.pro+LPAR()#q:s/pro/shmo/+RPAR()(#q.:s/builtin/shmiltin/)) demonstrates how colon modifiers and other qualifiers may be chained together. The ordinary qualifier `tt(.)' is applied first, then the colon