1

I have the following (MWE) shell script foo:

#!/bin/bash
ARGS=("$@") # all arguments
## => if it exists, we need to drop the argument "-D" here
ls -l ${ARGS[@]} | sort -fk8 

If foo is called with argument -D (the position in the list of arguments is unknown), how can I remove -D from the list of arguments? I found out that unset ARGS[${#ARGS[@]}-1] can drop the last argument, for example, but I'm not sure in which order the arguments are passed (so I first need to know at which place the argument is and then remove it in case it is provided).

2

2 Answers 2

8

The no-frills approach is to simply loop over the positional parameters, collecting all but -D into an array, and then use set -- to update the params:

for param; do 
    [[ ! $param == '-D' ]] && newparams+=("$param")
done
set -- "${newparams[@]}"  # overwrites the original positional params
3
  • In bash, you can even do newparams+=("$param").
    – choroba
    Commented Nov 11, 2017 at 12:13
  • How does the param name get set correctly?
    – minseong
    Commented Apr 1 at 12:26
  • 1
    @minseong Are you asking about for param;? When there is no in <word> following for <name> then bash will automatically populate <name> with the positional parameters (script args), one at a time.
    – B Layer
    Commented Apr 2 at 8:29
7

With zsh, use the ${array:#pattern} parameter expansion operator:

$ set foo -D -D bar '' $'a\nb'
$ printf '<%s>\n' "${@:#-D}"
<foo>
<bar>
<>
<a
b>

POSIXly:

for i do
  [ "$i" = -D ] || set -- "$@" "$i"
  shift
done
printf '<%s>\n' "$@"

BTW, you forgot the quotes, -- and -d:

ls -ld -- "$@"

If you want to sort by modification time, you can just use the -t option, here with -r (reverse) for oldest first:

ls -lrtd -- "$@"

Beware that if $ARGS is an empty array, it will list .. So you can do:

[ "$@" -eq 0 ] || ls -lrtd -- "$@"

To sort reliably based on the hour of the day irrespective of date, with zsh and a ls implementation that supports -U for not sorting:

zmodload zsh/stat # best in ~/.zshrc
bytime() zstat -LA REPLY -F%T  +mtime -- $REPLY
ls --full-time  -ldU -- .(e{'reply=("$@")'}o+bytime)

With limited shells like bash, it's very hard to sort files based on arbitrary metadata like that. Again, if you've got access to recent GNU tools, it's slightly easier:

[ "$#" -gt 0 ] && (
  export LC_ALL=C
  printf '%s\0' "$@" |
    sed -z 's|^-$|./-|' |
    xargs -r0 stat --printf '%y\t%n\0' -- |
    sort -zk2,2 |
    cut -zf 2-
) | xargs -r0 ls -lUd --

Portably (but still using the non-standard ls -U here), it's generally easier to resort to perl, like:

perl -MPOSIX -e '
  exec qw{ls -ldU --}, 
    map {$_->[1]}
    sort {$a->[0] cmp $b->[0]}
    map {[strftime("%T", localtime((lstat$_)[9])), $_]}
    @ARGV' -- "$@"

You must log in to answer this question.

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