Как удалить аргумент (из списка аргументов) в шелл-скрипте?

Вопрос или проблема

У меня есть следующий (MWE) скрипт оболочки foo:

#!/bin/bash
ARGS=("$@") # все аргументы
## => если он существует, нам нужно удалить аргумент "-D" здесь
ls -l ${ARGS[@]} | sort -fk8 

Если foo вызывается с аргументом -D (позиция в списке аргументов неизвестна), как я могу удалить -D из списка аргументов? Я узнал, что unset ARGS[${#ARGS[@]}-1] может удалить последний аргумент, например, но я не уверен, в каком порядке передаются аргументы (поэтому мне сначала нужно знать, на каком месте находится аргумент, а затем удалить его, если он указан).

Самый простой подход заключается в том, чтобы просто пройтись по позиционным параметрам, собрав все, кроме -D, в массив, а затем использовать set --, чтобы обновить параметры:

for param; do 
    [[ ! $param == '-D' ]] && newparams+=("$param")
done
set -- "${newparams[@]}"  # перезаписывает оригинальные позиционные параметры

В zsh используйте оператор расширения параметров ${array:#pattern}:

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

Согласно стандарту POSIX:

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

Кстати, вы забыли кавычки, -- и -d:

ls -ld -- "$@"

Если вы хотите сортировать по времени изменения, вы можете просто использовать опцию -t, здесь с -r (обратно) для наиболее старых:

ls -lrtd -- "$@"

Осторожно: если $ARGS является пустым массивом, он будет отображать .. Поэтому вы можете сделать:

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

Чтобы сортировать надежно по часу суток независимо от даты, с zsh и реализацией ls, которая поддерживает -U для несортировки:

zmodload zsh/stat # лучше всего в ~/.zshrc
bytime() zstat -LA REPLY -F%T  +mtime -- $REPLY
ls --full-time  -ldU -- .(e{'reply=("$@")'}o+bytime)

С ограниченными оболочками, такими как bash, очень трудно сортировать файлы на основе произвольных метаданных. Снова, если у вас есть доступ к современным инструментам GNU, это немного проще:

[ "$#" -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 --

Портативно (но все еще используя нестандартный ls -U здесь), обычно проще обратиться к perl, например:

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

Используйте ${@/-D}, чтобы удалить -D из позиционных параметров:

set -- a 1 2 -D -P; set -- ${@/-D}; echo "$*"
a 1 2 -P

с пользовательским массивом:

set -- -D -l foo.txt; ARGS=(${@/-D}); echo "${ARGS[*]}"; ls ${ARGS[*]}
-l foo.txt
-rw-r--r-- 1 j j 0 Dec 18 16:56 foo.txt

Ответ или решение

Чтобы удалить аргумент -D из списка аргументов в shell-скрипте на Bash, можно воспользоваться несколькими подходами. Наиболее простым и универсальным является использование цикла для обхода всех позиционных параметров и создания нового массива без нежелательного аргумента. В рамках данного ответа будет приведен пошаговый процесс решения задачи.

Пример скрипта

Рассмотрим ваш изначальный скрипт foo:

#!/bin/bash
ARGS=("$@") # все аргументы
ls -l ${ARGS[@]} | sort -fk8

Шаг 1: Определение нежелательного аргумента

Вам необходимо пройтись по всем элементам массива и отфильтровать аргумент -D.

Шаг 2: Создание нового массива без -D

Мы можем использовать цикл for, чтобы собрать все аргументы, кроме -D, в новый массив:

#!/bin/bash
ARGS=("$@") # все аргументы
newparams=() # инициализируем новый массив

for param in "${ARGS[@]}"; do 
    [[ "$param" != '-D' ]] && newparams+=("$param")
done

# Обновляем исходные аргументы
set -- "${newparams[@]}"
ls -l "${@}" | sort -fk8

Пояснение кода

  1. Инициализация массива: Мы сохраняем все переданные аргументы в массив ARGS.
  2. Фильтрация: С помощью цикла for мы проходим по каждому параметру в ARGS. Условие [[ "$param" != '-D' ]] позволяет добавить параметр в новый массив newparams только если он не равен -D.
  3. Обновление аргументов: С помощью команды set -- "${newparams[@]}" мы обновляем позиционные параметры, чтобы использовать их в дальнейших командах.
  4. Использование отфильтрованных аргументов: Наконец, мы выполняем команду ls -l с обновлённым списком аргументов.

Альтернативные методы

Во многих случаях вам может потребоваться более элегантное решение:

  1. Использование параметров оболочки Bash:

Вы можете воспользоваться встроенной системой замены параметров Bash:

#!/bin/bash
set -- "${@/-D/}"  # Удаляем -D
ls -l "$@" | sort -fk8

Однако этот метод не всегда может работать корректно с множественным вхождением аргумента -D, так как он удалит только первое вхождение. В этом случае стоит остановиться на первом подходе с сохранением аргументов в новом массиве.

Рекомендации

  • Не забывайте об экранировании: Всегда используйте кавычки вокруг переменных, чтобы избежать ошибок с пробелами в аргументах.
  • Проверяйте аргументы: При отсутствии аргументов стоит обрабатывать ситуацию, чтобы избежать вызова ls без параметров. Например, добавьте проверку:
if [ "$#" -gt 0 ]; then
    ls -l "$@" | sort -fk8
else
    echo "Нет файлов для отображения."
fi

Заключение

Использование цикла для фильтрации аргументов и обновление позиционных параметров через set — это эффективный способ управления входными данными в Bash-скрипте. Такие методы не только повышают читаемость и устойчивость вашего кода, но и делают его более адаптивным к возможным изменениям в списке аргументов.

Оцените материал
Добавить комментарий

Капча загружается...