2

I have a program that reads files in a certain order. For this example, they all have the same extension, txt

{program_name} {input 1...} {output}

I happen to name the output file name "a.txt" so it comes as the first result when I invoke ls, which shows the order of files being fed to this command:

{program_name} *.txt

I would like the first result to show up as the last one. How can I reverse the order of the star matching? If I need to use pipe, what's the syntax for feeding in arguments properly so that any special character or white-space will be quoted properly?

2
  • 1
    do you need a full reverse, or just have the first argument passed as the last? In the latter case, this will do wrapper(){ f=$1; shift; your_program "$@" "$f"; } – mosvy Feb 19 '19 at 20:02
  • @mosvy Yes, the latter. This works for me. Thank you. I am still hoping for an elegant solution. – Forethinker Feb 19 '19 at 20:13
5

Making use of zsh:

zsh -c 'program *.txt(.^on)'

The (.^on) is a zsh modifier for the *.txt pattern that makes it match only regular files (the .) and that will order (o) the resulting list in reverse (^) lexicographical order (n).

This would start a non-interactive zsh shell that would run program with the generated list of filenames as command line arguments. The command would properly handle reversing the list of arguments, even if they contained spaces or newlines etc. in the filenames.

Example:

$ touch {a..d}.txt
$ echo *.txt
a.txt b.txt c.txt d.txt
$ zsh -c 'echo *.txt(.^on)'
d.txt c.txt b.txt a.txt

A non-zsh solution:

set --
for fname in *.txt; do
    set -- "$fname" "$@"
done

program "$@"

or, using a named array in e.g. bash,

args=()
for fname in *.txt; do
    args=( "$fname" "${args[@]}" )
done

program "${args[@]}"

In both of these code snippets, an array is built up from the names matching the *.txt pattern. Each name is pushed onto the array at the front, so the effect is that the array ends up having the last name first.

In the first instance, we use the list of positional parameters as the array. That code should work in any sh-like shell.


If you just need the first name last in the list of arguments to your program, and can let the other names be in the order they are expanded in, then the following would do that (in any sh-like shell):

set -- *.txt
fname=$1
shift
program "$@" "$fname"

Alternatively, using bash-specific syntax and a named array,

args=( *.txt )
program "${args[@]:1}" "${args[0]}"

"${args[@]:1}" would expand to all elements of the args array (individually quoted) from element 1 onwards.

Testing again with echo:

$ args=( *.txt )
$ echo "${args[@]:1}" "${args[0]}"
b.txt c.txt d.txt a.txt
3
  • For your first example, I get: a.txt b.txt d.txt c.txt even after I run zsh without .zshrc file using: zsh -d -f – Forethinker Feb 19 '19 at 20:08
  • @Forethinker Now fixed. Thanks for the heads up! – Kusalananda Feb 19 '19 at 20:34
  • Awesome. Go zsh! – Forethinker Feb 20 '19 at 1:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.