3

Here is extension of the question: How do I limit the number of files printed by ls? with additional condition: how to print results in as many columns as fit to the current terminal width — as ls does by default.

I understand how to limit number of files by head command but I have not come up with the solution on how to print files in columns in the same way as ls does by default but after processing through pipe.

I tried ls -C flag (from man ls: -C list entries by columns) with piping to cat. But its output differs from ls out:

for i in {1..30}; do touch file$i; done; echo '"ls" out:'; ls; echo '"ls -C | cat" out:'; ls -C | cat

"ls" out:
file1   file11  file13  file15  file17  file19  file20  file22  file24  file26  file28  file3   file4  file6  file8
file10  file12  file14  file16  file18  file2   file21  file23  file25  file27  file29  file30  file5  file7  file9

"ls -C | cat" out:
file1   file12  file15  file18  file20  file23  file26  file29  file4  file7
file10  file13  file16  file19  file21  file24  file27  file3   file5  file8
file11  file14  file17  file2   file22  file25  file28  file30  file6  file9

"ls" out gives 2 line. And "ls -C | cat" out gives 3 lines. Does this may be ls -C bug?

I tried column command. But when file with longer name is present it gives different result than ls.

mv file30 file-with-long-name; ls
file1   file11  file13  file15  file17  file19  file20  file22  file24  file26  file28  file3  file5  file7  file9
file10  file12  file14  file16  file18  file2   file21  file23  file25  file27  file29  file4  file6  file8  file-with-long-name

ls | cat | column -c $(tput cols)
file1           file14          file19          file23          file28          file6
file10          file15          file2           file24          file29          file7
file11          file16          file20          file25          file3           file8
file12          file17          file21          file26          file4           file9
file13          file18          file22          file27          file5           file-with-long-name

ls gives 2 lines. And ls | cat | column -c $(tput cols) gives 5 lines. tput cols — calculates terminal width. Is there may be alternative to column that will not format all columns width according to the longest file name, but will do this like ls does?

Is there may be alternative command to ls to achieve such printing with limiting max file numbers? (But I also would like to have similar to ls files coloring.)

With each cd to another directory I want automatically to show current directory's content. This can be done with putting ls to $PROMPT_COMMAND environment variable. But there is a problem with directories that contain too many files. They will make much noise / litter in the console. I want to restrict them to limit ls output.

4
  • This question is similar to: Why *not* parse `ls` (and what to do instead)?. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem. Commented Jul 31 at 9:01
  • in other words, whatever you're doing, processing the output of ls with a pipe is probably not a good idea. What are you doing in your pipe? Maybe there's a simple solution to let ls do what you want it to do, namely output things in the shape you want, but not do the things you don't want to do (parsing variable output from ls). Commented Jul 31 at 9:02
  • 1
    Out of curiosity: what are you trying to achieve with this? I mean, I get what you're saying, but what's the purpose/benefit?
    – tink
    Commented Jul 31 at 9:06
  • 2
    @tink With each cd to another dir I want automatically to show current dir content. This can be done with putting ls to $PROMPT_COMMAND env var. But there is problem with directories that contain much amount of files. They will make much noise / litter in console. I want to restrict them to limit ls output. Commented Jul 31 at 9:13

2 Answers 2

5

With zsh:

ls -d -- *([1,10])

Would list the first 10 files (in lexical order).

ls -t -d -- *(om[1,10])

For the first 10 files ordered by modification time (with GNU ls, you can replace -t with -U to skip the sorting by mtime as zsh has already sorted them).

See also oL (with ls -S) to order by Length (Size), n (with GNU ls -v) for numeric (version) order, O instead of o to revert the order (with ls -r) and more. Use [-10,-1] for the last 10 ones.

zsh's print builtin can also print in columns with its -C option:

print -rC4 -- *([1,10])

To print raw on 4 Columns for instance.

It also has a chpwd hook function (and chpwd_functions array) which is invoked any time the current working directory changes (more versatile than bash $PROMPT_COMMAND whose equivalent would be the precmd hook function or wrapping the cd/pushd/popd builtins),

so you can add to ~/.zshrc:

list-first-few-files() {
  local files=( *(N) )
  if (( $#files )); then
    ls -dF -- $files[1,10]
    if (( $#files > 10 )) print ... and $(($#files - 10)) more.
  else
    print Directory has no non-hidden files
  fi
}
autoload -Uz add-zsh-hook
add-zsh-hook chpwd list-first-few-files

In bash (or ksh93 where bash copied that syntax from), you could do:

lsn() {
  ls -d -- "${@:2:$1}"
}

Then:

lsn 10 *

bash doesn't have glob qualifiers to change the order of glob expansions but next version 5.3 will have a GLOBSORT global variable with which you can change the default sorting order with which you'll be able to clumsily do a subset of what you can do with zsh's oOn^- glob qualifiers.

About your ls -C | cat bug, it's just that the output is not going to a terminal, so ls assumes a 80 column display width instead of requesting the width of the terminal it displays its output on.

You can adjust that default width with the -w option (with GNU ls at least) or the $COLUMNS environment variable (as @ChrisDavies says, bash¹ like zsh set that variable automatically based on the width of the terminal, but do not export it to the environment by default).

Other side effect of ls output not going to a terminals are:

  • control characters are no longer escaped (which can be quite dangerous with malicious file names). That also means that using head is invalid when there are files that contain newline characters (one of those control characters).
  • colouring if you have an ls alias that adds --color=auto for GNU ls is no longer done.

Also, if you're going to use head on the columned output, you should use -x (aka --format=across with GNU ls) instead of -C or you won't get the first files. So:

ls -w "$COLUMNS" --color=always -xqF | head -n 4

For the first 4 lines of the output of a columned ls.


¹ in the case of bash only when interactive

2
  • In man ls there is inaccuracy: -d, --directory - list directories themselves, not their contents. Should be like: list files / directories themselves since file names are also printed. Commented Jul 31 at 17:58
  • 1
    @AntonSamokat, for the GNU implementation of ls, the proper manual is in texinfo format which you can view as HTML online or locally or with info: info -- ls --directory for instance. You can also look at the POSIX specification for what to expect portably. Commented Jul 31 at 19:02
5

The bash shell (and others) maintains a POSIX variable called $COLUMNS that represents the current known width of the terminal device. When you run a command such as ls (implicitly ls -C) the tool checks the width of the terminal, if any, and adjusts the number of columns of file names to fit the available width. If the width is unavailable then ls falls back to assuming a standard 80 column width output.

When you run a pipeline such as ls -C | head -n4, because the $COLUMNS variable is not exported, it's unavailable to the ls command. So ls -C here defaults to an 80 column width output.

You can fix this by making $COLUMNS available to the ls command:

COLUMNS=$COLUMNS ls -C | head -n4

Example using a terminal device current set to 132 characters width

ls -C | head -n1
file1   file12  file15  file18  file20  file23  file26  file29  file4  file7

COLUMNS=80 ls -C | head -n1
file1   file12  file15  file18  file20  file23  file26  file29  file4  file7

COLUMNS=132 ls -C | head -n1
file1   file11  file13  file15  file17  file19  file20  file22  file24  file26  file28  file3   file4  file6  file8

Be aware that you cannot use $COLUMNS to change the number of columns presented by a bare ls (or ls -C). Some versions of the tool prefer to ask the terminal device directly for its column width and only if that fails do they fall back to using $COLUMNS.

# COLUMNS is ignored for an interactive terminal-based session
COLUMNS=132 ls -C
COLUMNS=80 ls -C
5
  • 1
    In ls -C, $COLUMNS is not available to ls either unless exported, but in that case ls (well GNU ls at least) requests the size of the terminal via a TIOCGWINSZ ioctl on its stdout (which obviously fails when that's a pipe) Commented Jul 31 at 9:46
  • 1
    Testing with GNU ls on a Debian system, the suggested answer works Commented Jul 31 at 9:50
  • 2
    Yes, I'm not saying it doesn't. GNU ls will use $COLUMNS indeed if the output is not to a terminal. Commented Jul 31 at 9:54
  • 2
    Note that the 2024 edition of the POSIX standard is now out and its HTML version available at pubs.opengroup.org/onlinepubs/9799919799.2024edition Commented Jul 31 at 10:10
  • For those dealing with shells that don’t provide the $COLUMNS variable, you can usually get the same information using tput cols if the tput command from ncurses is available on the system. Commented Jul 31 at 20:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .