Как изменить аргументы оболочки?

POSIXly вы можете сделать:

sort -t '(' -k2n < file

Это устанавливает разделитель полей на (и сортирует по второму полю (или, скорее, по части строки, начинающейся со второго поля )в числовом виде.

В качестве альтернативы вы можете оставить разделитель полей по умолчанию (для перехода от не-пустого поля к пустому ), где 5-е поле будет похоже на " (12"и использовать:

sort -k5.3n < file

(, т. е. численно отсортировать часть строки, начинающуюся с 3-го -го символа 5-го -го поля ).

Для ничьих в игру вступает последний -порядок сортировки, и это лексическое сравнение полной строки (, которое удобно здесь даст вам хронологический порядок ).

Если вы хотите отсортировать связи по имени диска, вы можете использовать:

sort -t '(' -k2n -k1.21

(второй ключ — часть строки, начинающаяся с 21 st символа, лексическое сравнение)

9
07.01.2020, 13:38
3 ответа

Переносимо, массивы не требуются (только позиционные параметры )и работает с пробелами и новыми строками:

flag=''; for a in "$@"; do set -- "$a" ${flag-"$@"}; unset flag; done

Пример:

$ set -- one "two 22" "three
> 333" four

$ printf '<%s>' "$@"; echo
<one><two 22><three
333><four>

$ flag=''; for a in "$@"; do set -- "$a" ${flag-"$@"}; unset flag; done

$ printf '<%s>' "$@"; echo
<four><three
333><two 22><one>

Значение flagуправляет расширением ${flag-"$@"}. Когда установлено flag, оно расширяется до значения flag(, даже если оно пусто ). Таким образом, когда flagравно flag='', ${flag....}расширяется до пустого значения и удаляется оболочкой, поскольку оно не заключено в кавычки. Когда flagсбрасывается, значение ${flag-"$@"}расширяется до значения справа от -, это расширение "$@", поэтому оно становится всеми позиционными аргументами (в кавычках, нет пустое значение будет стерто ). Кроме того, переменная flagстановится стертой (неустановленной ), что не влияет на следующий код.

14
27.01.2020, 20:03

Если мы не хотим использовать массив для временного хранения, мы можем использовать тот факт, что forцикл всегда перебирает неизменный статический набор элементов. В некотором смысле мы можем использовать сам цикл как временное хранилище позиционных параметров при перестроении списка в обратном порядке.

Чтобы сделать это, нам также нужно очистить список на первой итерации. В приведенном ниже коде используется простой флаг, чтобы определить, нужно ли это делать или нет. Когда список пуст, флаг переключается.

flag=true
for value do
    if "$flag"; then
        set --
        flag=false
    fi

    set -- "$value" "$@"
done

К сожалению, это довольно медленно, так как список позиционных параметров эффективно перестраивается на каждой итерации(set -- some-listустанавливает все позиционные параметры ). Оболочке bashтребуется около 50 секунд, чтобы преобразовать целые числа от 1 до 10000, а zsh— чуть более 15 секунд.

Использование уловки Исаака с ${flag-"$@"}(, которая расширяется до "$@"только в том случае, если flagне установлено ), на самом деле все работает медленнее; 1 минута 50 секунд (! )в bashи 25 секунд в zsh.

Я предполагаю, что это связано с некоторыми особенностями реализации в том, как оболочки выполняют тест на $flagи/или расширяют "$@"для расширения ${flag-"$@"}(оболочка, возможно, расширяет "$@"дважды внутри? ).


Если мы позволим себе использовать массив в качестве временного хранилища, (это не будет стандартным ,но все же довольно переносимый поскольку мы часто знаем, для какой оболочки мы пишем наши скрипты ), мы можем использовать значение$#(количество позиционных параметров )в качестве индекса для хранения текущее значение при переборе позиционных параметров. Уменьшение этого значения с помощью shiftна каждой итерации дает эффект вставки значений от конца массива к началу.

В bashмассивы начинаются с индекса 0, а поскольку shiftидет после присваивания, последний позиционный параметр будет сохранен с индексом 1, а не 0. Это не влияет на то, как работает код в bash, он по-прежнему будет генерировать правильный результат, но он также работает в zsh(, который по умолчанию использует 1 -индексов массива на основе ).

Код:

tmp=()
for value do
    tmp[$#]=$value
    shift
done

set -- "${tmp[@]}"

При использовании bashили zshдля обращения целых чисел от 1 до 10000 требуется около 0,6 секунды.

10
27.01.2020, 20:03

Скопировано из этого моего ответа в Bash -вывести перевернутый список файлов с помощью glob , чтобы перевернуть список позиционных параметров POSIXly:

eval "set -- $(awk 'BEGIN {for (i = ARGV[1]; i; i--) printf " \"${"i"}\""}' "$#")"

Или чуть более разборчиво на несколько строк:

eval "set -- $(
  awk '
    BEGIN {
      for (i = ARGV[1]; i; i--)
        printf " \"${" i "}\""
    }' "$#"
)"

Идея заключалась в том, чтобы использовать awkдля создания шелл-кода set -- "${3}" "${2}" "${1}"для evalдля интерпретации, например, когда "$@"имеет 3 элемента.

Для больших списков это, вероятно, будет значительно быстрее, чем использование цикла оболочки, особенно такого, который перестраивает список на каждой итерации. Код awkможно заменить циклом оболочки, который дает тот же результат (, что и @mosvy, показанный в комментариях ), но в моих тестах с bash5+gawk4.1 он по-прежнему в два раза медленнее, за исключением очень короткие списки.

В zshвы должны использовать флаг параметра Oa, который явно предназначен для реверсирования массива:

set -- "${(Oa)@}"

В моей системе (немного медленнее, чем у @Kusalananda ), и в списке позиционных параметров, полученном с set $(seq 10000), с bash5 + gawk4.2.1,этот подход evalзанимает 0,4 с, в то время как @Kusalananda занимает 1 минуту, а @Isaac занимает 2 минуты(zshподход Oaзанимает около 2 миллисекунд ).

Для shи awkиз busybox 1.30.1 эти тайминги становятся :0,06 с, 11 с, 11 с соответственно.

10
27.01.2020, 20:03

Теги

Похожие вопросы