114
array=${ls -d */}
echo ${array[@]}  

I have three directories: ww ee qq. I want them in an array and then print the array.

codeforester
  • 39,467
  • 16
  • 112
  • 140
Jordin Youssef
  • 1,147
  • 2
  • 7
  • 4
  • 1
    Does this answer your question? [Reading filenames into an array](https://stackoverflow.com/questions/10981439/reading-filenames-into-an-array) – akostadinov May 20 '20 at 06:39

3 Answers3

154

It would be this

array=($(ls -d */))

EDIT: See Gordon Davisson's solution for a more general answer (i.e. if your filenames contain special characters). This answer is merely a syntax correction.

Westy92
  • 19,087
  • 4
  • 72
  • 54
Aaron Okano
  • 2,243
  • 1
  • 12
  • 5
  • 20
    This will not work correctly if the directory names contain spaces. – chepner Sep 19 '13 at 13:30
  • 1
    In order to solve the issue of spaces or special characters, declare the array without any values first, then use the built-in `ARR+=('$(ls -d */)')` for editing elements in an array, while using single quotes to deal with special characters, to add or remove elements to that array. Single quotes preserves the literal characters and removes any need for escapes to deal with them in directory names. – Yokai Jan 10 '18 at 09:12
  • @Yokai Your suggestion is sounds wonderful but didn't work for me on Ubuntu 16.04 or MacOs. Bash expands the code between the single quotes like this: `declare -a ARR; ARR+=('$(ls -d "$INPUT_DIR"/*)')` returns only `$(ls -d $INPUT_DIR/*)` – Agile Bean Jun 19 '18 at 14:34
  • 3
    My mistake. That should have been double quotes so the command substitution can be expanded. `ARR+=("$(ls -d */)")` should work. If not just let me know. – Yokai Jul 10 '18 at 02:50
  • 3
    @Yokai, that adds only *one* element total (with a bunch of newlines in it), not one element per directory. – Charles Duffy Feb 19 '19 at 00:05
  • `IFS=$'\n' array=($(ls -1 -d */))`. Use the `-1` option to put every item on its own line and use the `IFS` variable to make line-breaks be the array separator. – JellicleCat Mar 28 '20 at 17:59
  • @JellicleCat, ...though that isn't correct in all cases either. "All cases" is a pretty substantial set -- it's perfectly legal for a file to have an actual newline in its name (`touch $'foo\nbar'`). – Charles Duffy Jun 10 '21 at 00:37
  • 2
    According to shellcheck, the following would be a better option: `mapfile -t contents < <(ls -d -- *)` There were two issues with the original solution: (i) spaces in paths were not supported and (ii) filenames starting with a dash were interpreted as arguments to `ls` rather than filenames. – Christoph Thiede Sep 03 '21 at 17:30
  • Take a look at [this](https://stackoverflow.com/a/18885149/7015849) answer for a simple, non-`ls`-way which works with spaces. – spawn Jun 08 '22 at 10:47
101

Whenever possible, you should avoid parsing the output of ls (see Greg's wiki on the subject). Basically, the output of ls will be ambiguous if there are funny characters in any of the filenames. It's also usually a waste of time. In this case, when you execute ls -d */, what happens is that the shell expands */ to a list of subdirectories (which is already exactly what you want), passes that list as arguments to ls -d, which looks at each one, says "yep, that's a directory all right" and prints it (in an inconsistent and sometimes ambiguous format). The ls command isn't doing anything useful!

Well, ok, it is doing one thing that's useful: if there are no subdirectories, */ will get left as is, ls will look for a subdirectory named "*", not find it, print an error message that it doesn't exist (to stderr), and not print the "*/" (to stdout).

The cleaner way to make an array of subdirectory names is to use the glob (*/) without passing it to ls. But in order to avoid putting "*/" in the array if there are no actual subdirectories, you should set nullglob first (again, see Greg's wiki):

shopt -s nullglob
array=(*/)
shopt -u nullglob # Turn off nullglob to make sure it doesn't interfere with anything later
echo "${array[@]}"  # Note double-quotes to avoid extra parsing of funny characters in filenames

If you want to print an error message if there are no subdirectories, you're better off doing it yourself:

if (( ${#array[@]} == 0 )); then
    echo "No subdirectories found" >&2
fi
Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
  • 3
    Thanks for your comment, even though not marked as the answer it was quite informative and I appreciate it. – Jake H Sep 19 '13 at 06:54
  • What if you don't want the `/` in the dir name? – shinzou Aug 06 '17 at 07:00
  • The problems/issues outlined in the greg's wiki are not things that cannot be easily dealt with with mere conditional constructs. `ls` is a simple command. It's output is strings. Bash is excessively good at parsing strings. The proper regex is all that is necessary to safely parse `ls` output. – Yokai Oct 29 '17 at 04:56
  • 1
    @Yokai Different versions of `ls` do different things with funny/nonprinting characters in filenames, so there's no consistent way to parse its output into a list of filenames. And as I said, it's not actually doing anything useful: if you correctly parse the output of `ls -d */`, you wind up with exactly the same thing you'd get directly from `*/` (except when there are no matches, and that's easy to check for). – Gordon Davisson Oct 29 '17 at 05:39
  • So to summarize: `A lazy sysadmin is a good sysadmin.` Perhaps my own `ls` parsing is specific to the version I have, or that I am used to, but so far they have had no issues on the linux distros I have tested them on. I can only speak from experience. – Yokai Oct 29 '17 at 07:53
  • After discussing this topic in my bash group on facebook, we found out that `shopt` is not included by default in debian or it's derivative distros so it may not be a portable enough option. I do like the glob idea though. – Yokai Oct 29 '17 at 09:02
  • Other user tried to read a non-existent man page for shopt which doesn't exist in debian. She has it though. Crisis averted. – Yokai Oct 29 '17 at 09:32
  • @Yokai Doesn't GNU `ls` show nonprinting characters as "?" by default? That can cause some problems. BTW, `shopt` is a bash builtin, so it doesn't have a man page (it's listed in the `bash` man page and bash `help`). It's also not available in more basic shells like dash (which [recent Debians use as /bin/sh](https://wiki.ubuntu.com/DashAsBinSh)). But dash doesn't have arrays either, so if you're using arrays you need to use bash. – Gordon Davisson Nov 01 '17 at 01:43
  • I know `shopt` is a bash built-in. I said the `other user` tried to read a `non-existent man page`. – Yokai Nov 04 '17 at 07:56
  • @Yokai, trusting in experience only lasts for so long. In one former employer, we had a data loss event due to a backup-management script with unquoted expansions -- it was considered safe because the directory was only written to by a script that was supposed to generate only filenames matching `[0-9a-f]{24}`. Until one day a new program writing that directory was added, which happened to have a bug in a C library it used which could corrupt its memory. One day that corruption happened, a string that contained a whitespace-surrounded `*` was dumped into the filename buffer... – Charles Duffy Feb 19 '19 at 00:07
  • @Yokai, ...and when the program responsible for deleting old files in that directory attempted to remove it, it deleted *everything* in that directory -- terabytes of logs used to support customer billing. This may be only a once-in-a-career event, but it was, suffice to say, enough of a disaster in terms of time and expense incurred to justify the extra time it would have taken someone to write better scripts for that entire career. – Charles Duffy Feb 19 '19 at 00:08
  • @Yokai, ...one counterargument is "okay, it *might* matter sometimes, but I'll worry about it when I'm writing something for use in a critical scenario", but if you take that approach you need to actually think that you're in a critical scenario before being careful (and can't ever reuse software in a critical scenario if it was built with a less-critical case in mind earlier); whereas *always* being careful builds a library of habits that supports generating software that does the Right Thing whether or not a failure case was envisioned as relevant or not at time-of-development. – Charles Duffy Feb 19 '19 at 00:12
16

This would print the files in those directories line by line.

array=(ww/* ee/* qq/*)
printf "%s\n" "${array[@]}"
konsolebox
  • 72,135
  • 12
  • 99
  • 105
  • Well, it's not `ls`, but for the purpose of the OP, `ls` does not seem necessary here. And it works with spaces. Nice and simple! – spawn Jun 08 '22 at 10:46