7

Is there a way to find the length of the array *(files names) in zsh without using a for loop to increment some variable?

I naively tried echo ${#*[@]} but it didn't work. (bash syntax are welcome as well)

6
  • 1
    By "length of the array" do you mean its number of elements? And do you want to get this without actually defining this array (* suggest you want to use shell globing mechanism here)?
    – jimmij
    CommentedFeb 9, 2019 at 1:36
  • Oops, you're right i should have asked the other way around, I'll edit it.
    – Cristiano
    CommentedFeb 9, 2019 at 1:48
  • @Cristiano: zsh doesn't have anything to do with it. * is not an array in the way you are using it, it is a shell glob. Arrays have nothing to do with your question unless you create an array as Jeff did in his answer. Your question is "How do I find how many files are in the current directory"
    – jesse_b
    CommentedFeb 9, 2019 at 1:49
  • @Jess_b But it acts like an array don't you think? echo *[0] in zsh prints the 1st file name...
    – Cristiano
    CommentedFeb 9, 2019 at 2:07
  • @Cristiano: I believe that is a zsh specific glob qualifier but still doesn't make the glob an array
    – jesse_b
    CommentedFeb 9, 2019 at 2:15

2 Answers 2

7

${#*[@]} would be the length of the $* array also known as $@ or $argv, which is the array of positional parameters (in the case of a script or function, that's the arguments the script or function received). Though you'd rather use $# for that.

* alone is just a glob pattern. In list context, that's expanded to the list of files in the current directory that match that pattern. As * is a pattern that matches any string, it would expand to all file names in the current directory (except for the hidden ones).

Now you need to find a list context for that * to be expanded, and then somehow count the number of resulting arguments. One way could be to use an anonymous function:

() {echo There are $# non hidden files in the current directory} *(N) 

Instead of *, I used *(N) which is * but with the N (for nullglob) globbing qualifier which makes it so that if the * pattern doesn't match any file, instead of reporting an error, it expands to nothing at all.

The expansion of *(N) is then passed to that anonymous function. Within that anonymous function, that list of file is available in the $@/$argv array, and we get the length of that array with $# (same as $#argv, $#@, $#* or even the awkward ksh syntax like ${#argv[@]}).

4
  • Would you actually call $* an array at all? A "string" would be a better word for it, IMHO. Maybe it's different in zsh?
    – Kusalananda
    CommentedFeb 10, 2019 at 7:58
  • @Kusalananda how could it be a string if the elements it's made of are words/strings themselves? When $* is used inside double quotes, the words it's made of ($1, $2, ...) will be joined by the first char of IFS without its elements being split on IFS or spaces before that: (set -- 'a/b' 'a b'; IFS=:/; echo "<$*>") (this latter digression is to illustrate that $* is not somehow a string split an then rejoined in this particular syntax).
    – user313992
    CommentedFeb 10, 2019 at 10:05
  • @mosvy I know he mentions $* and $@ unquoted, but "$*" is at least clearly a string whereas "$@" is not a single string. Calling $* an array and saying another name for it is $@ seems to confuse the purpose of these two variables.
    – Kusalananda
    CommentedFeb 10, 2019 at 10:11
  • @Kusalananda, it's the array of positional parameters. zsh and yash have real arrays and arrays whose indice start at 1, so they can represent that array as a normal array variable. That array goes by the *, @ and argv names in zsh. Except that $@ is special inside double quotes on list contexts like it was in the Bourne shell. $* inside double quotes expands to the concatenation of the elements like $argv or $argv[*] (or even $*[*]) do, but that doesn't make it less that $argv is an array variable, not a scalar variable.CommentedFeb 17, 2019 at 18:02
5
files=(*) printf 'There are %d files\n' "${#files[@]}" 

or

set -- * printf 'There are %d files\n' "$#" 

You have to name the array first (as I did above with files) or use the built-in array $@ by populating it with the wildcard, as I did in the second example. In the former, the "length" (number of files) of the array is done with the ${#arrayname[@]} syntax. The number of elements in the built-in array is in $#.

4
  • So the * acts as a regex expression? I thought that it was a special array... although surprise me that there is no such array...
    – Cristiano
    CommentedFeb 9, 2019 at 1:59
  • wait, in zsh *[0] or *[1] gives me some file name... now I'm puzzled.
    – Cristiano
    CommentedFeb 9, 2019 at 2:02
  • The array is $@... $* equals a single string with each argument separated by $IFS (usually space). ZSH and Bash doesn't handle usage of an "index" in space-delimited strings the same way.
    – svin83
    CommentedFeb 9, 2019 at 3:19
  • Cristiano, the *[0] in zsh would expand to filenames that start with anything (or nothing) followed by any of the following characters: 0. The square brackets designates a set of characters in that position. You likely have files that end with a zero in your current directory. To tell zsh to give you back the first element of a * wildcard, you'd use parenthesis for array syntax: print -l *([1]) -- noting that zsh arrays start at 1 unless you set $KSH_ARRAYS
    – Jeff Schaller
    CommentedFeb 9, 2019 at 17:14

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.