Индексация и изменение $ массива параметров Bash

Эти команды вставят вывод strftime("%c") право, где Ваш курсор:

:exe ":normal i" . strftime("%c")

и

:call feedkeys("i". strftime("%c"))

Существуют другие способы сделать то, что Вы хотите (как, например, те, которые на ответе Mikel).

Править: Еще лучше, для оперативной вставки, используйте = зарегистрируйтесь, как Chris Johnsen описывает

11
26.04.2011, 11:55
5 ответов

POSIX

Для нормализации наклонных черт во всех параметрах я буду использовать вращающийся прием аргумента: сдвиг $1 прочь, преобразуйте его и поместите результат в конце списка параметров. Если Вы делаете это столько же время, сколько существуют параметры, Вы преобразовали все параметры, и у Вас есть они назад в порядке.

Для второй части кода я изменил Вашу логику, чтобы менее сбивать с толку: внешний цикл выполняет итерации по параметрам, и внутренний цикл выполняет итерации по компонентам контура. for x; do … done выполняет итерации по позиционным параметрам, это - удобная идиома. Я использую совместимый POSIX способ соответствовать строке против шаблона: case создать.

Протестированный с тире 0.5.5.1, pdksh 5.2.14, удар 3.2.39, удар 4.1.5, ksh 93 +, zsh 4.3.10.

Примечание стороны: кажется, существует ошибка в ударе 4.1.5 (не в 3,2): если шаблон случая "${common_path%/}"/*, один из тестовых сбоев.

posix_path_common () {
  for tmp; do
    tmp=$(printf %s. "$1" | tr -s "/")
    set -- "$@" "${tmp%.}"
    shift
  done
  common_path=$1; shift
  for tmp; do
    while case ${tmp%/}/ in "${common_path%/}/"*) false;; esac; do
      new_common_path=${common_path%/*}
      if [ "$new_common_path" = "$common_path" ]; then return 1; fi
      common_path=$new_common_path
    done
  done
  printf %s "$common_path"
}

удар, ksh

Если Вы находитесь в ударе (или ksh), можно использовать массивы — я не понимаю, почему Вы, кажется, ограничиваете себя позиционными параметрами. Вот версия, которая использует массив. Я должен признать, что это не особенно более ясно, чем версия POSIX, но это действительно избегает начальной буквы n^2 перестановка.

Для части нормализации наклонной черты я использую конструкцию ksh93 ${foo//PATTERN/REPLACEMENT} создайте для замены всех случаев PATTERN в $foo REPLACEMENT. Шаблон +(\/) для соответствия одному или нескольким режут; под ударом, shopt -s extglob должен быть в действительности (эквивалентно, запустите удар с bash -O extglob). Конструкция set ${!a[@]} устанавливает позиционные параметры на список нижних индексов массива a. Это обеспечивает удобный способ выполнить итерации по элементам массива.

Для второй части, я та же логика цикла как версия POSIX. На этот раз я могу использовать [[ … ]] так как все оболочки, предназначенные здесь, поддерживают его.

Протестированный с ударом 3.2.39, колотите 4.1.5, ksh 93 +.

array_path_common () {
  typeset a i tmp common_path new_common_path
  a=("$@")
  set ${!a[@]}
  for i; do
    a[$i]=${a[$i]//+(\/)//}
  done
  common_path=${a[$1]}; shift
  for tmp; do
    tmp=${a[$tmp]}
    while [[ "${tmp%/}/" != "${common_path%/}/"* ]]; do
      new_common_path="${common_path%/*}"
      if [[ $new_common_path = $common_path ]]; then return 1; fi
      common_path="$new_common_path"
    done
  done
  printf %s "$common_path"
}

zsh

К сожалению, zsh недостает ${!array[@]} функция для выполнения ksh93 версии как есть. К счастью, zsh имеет две функции, которые делают первую часть бризом. Можно индексировать позиционные параметры, как будто они были @ массив, таким образом, нет никакой потребности использовать промежуточный массив. И zsh имеет итеративную конструкцию массива: "${(@)array//PATTERN/REPLACEMENT}" выполняет замену шаблона на каждом элементе массива в свою очередь и оценивает к массиву результатов (смутно, Вам действительно нужны двойные кавычки даже при том, что результатом являются несколько слов; это - обобщение "$@"). Вторая часть чрезвычайно неизменна.

zsh_path_common () {
  setopt local_options extended_glob
  local tmp common_path new_common_path
  set -- "${(@)@//\/##//}"
  common_path=$1; shift
  for tmp; do
    while [[ "${tmp%/}/" != "${common_path%/}/"* ]]; do
      new_common_path="${common_path%/*}"
      if [[ $new_common_path = $common_path ]]; then return 1; fi
      common_path="$new_common_path"
    done
  done
  printf %s "$common_path"
}

Тесты

Мои решения минимально тестируются и комментируются. Я изменил синтаксис Ваших тестовых сценариев для парсинга под оболочками, которые не имеют $'…' и отказы отчета более удобным способом.

do_test () {
  if test "$@"; then echo 0; else echo $? "$@"; failed=$(($failed+1)); fi
}

run_tests () {
  function_to_test=$1; shift
  failed=0
  do_test "$($function_to_test /a/b/c/d /a/b/e/f; echo x)" = /a/bx
  do_test "$($function_to_test /long/names/foo /long/names/bar; echo x)" = /long/namesx
  do_test "$($function_to_test / /a/b/c; echo x)" = /x
  do_test "$($function_to_test a/b/c/d a/b/e/f ; echo x)" = a/bx
  do_test "$($function_to_test ./a/b/c/d ./a/b/e/f; echo x)" = ./a/bx
  do_test "$($function_to_test '
/
/
' '
/
'; echo x)" = '
/
'x
  do_test "$($function_to_test --/-- --; echo x)" = '--x'
  do_test "$($function_to_test '' ''; echo x)" = x
  do_test "$($function_to_test /foo/bar ''; echo x)" = x
  do_test "$($function_to_test /foo /fo; echo x)" = x
  do_test "$($function_to_test '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
' '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
'; echo x)" = '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
'x
  do_test "$($function_to_test /foo/bar //foo//bar//baz; echo x)" = /foo/barx
  do_test "$($function_to_test foo foo; echo x)" = foox
  do_test "$($function_to_test /fo /foo; echo x)" = x
  if [ $failed -ne 0 ]; then echo $failed failures; return 1; fi
}
16
27.01.2020, 19:57
  • 1
    +50, просто ошеломите. Больше, чем я попросил, во всяком случае. Вы, сэр, являетесь удивительными. –  l0b0 28.04.2011, 10:59

Почему Вы только не используете 1$, 2$.. 9$, $ {10}, $ {11}.. и так далее? Это - еще больше СУШИЛКИ, чем, что Вы пытаетесь сделать :)

Больше на отношении между $number и $:

$ можно рассмотреть как сокращение от "всех элементов массива, содержащего все аргументы"

Так, $ является своего рода стенографией $ {args} (args, вот 'виртуальный' массив, содержащий все аргументы - не реальная переменная, обратите внимание),

1$ является $ {args [1]}, 2$ $ {args [2]}, и так далее.

Когда Вы совершите нападки [9], используйте фигурную скобку: $ {10} является $ {args [10]}, $ {11} является $ {args [11]} и так далее.


Косвенно используйте параметр командной строки

argnum=3  # You want to get the 3rd arg
do-something ${!argnum}  # Do something with the 3rd arg

Пример:

argc=$#
for (( argn=1; argn<=argc; argn++)); do
    if [[ ${!argn} == "foo" ]]; then
        echo "Argument $argn of $argc is 'foo'"
    fi
done
6
27.01.2020, 19:57
  • 1
    Очевидная оборотная сторона необходимости использовать $ *number* - то, что Вы не можете использовать индексную переменную в качестве с ${args[$i]}. –  intuited 16.04.2011, 08:08
  • 2
    @intuited затем использует косвенность; я отредактирую свой ответ. –  pepoluan 16.04.2011, 15:16

Первый аргумент используется, с одной стороны, и остальные для чего-то еще,

Я думаю, что Вы хотите, shift

$ set one two three four five
$ echo $@
one two three four five
$ echo $1
one
$ foo=$1
$ echo $foo
one
$ shift
$ echo $@
two three four five
$ shift 2
$ echo $@
four five
$ echo $1
four
5
27.01.2020, 19:57

Я не вполне знаю, почему Вы только не используете $1$ 2 и т.д. но.. Это может удовлетворить Вашим потребностям.

$ script "ed    it" "cat/dog"  33.2  \D  

  echo "-------- Either use 'indirect reference'"
  for ((i=1;i<=${#@};i++)) ;do
    #  eval echo \"\$$i\" ..works, but as *pepoluan* 
    #    has pointed out: echo "${!i}" ..is better.
    echo "${!i}"
  done
  echo "-------- OR use an array"
  array=("$@")
  for ((i=0;i<${#array[@]};i++)) ;do
    echo "${array[$i]}" 
  done
  echo "-------- OR use 'set'"
  set  "$@"
  echo "$1"
  echo "$2"
  echo "$3"
  echo "$4"

вывод

  -------- Either use 'indirect reference'
  ed    it
  cat/dog
  33.2
  D
  -------- OR use an array
  ed    it
  cat/dog
  33.2
  D
  -------- OR use 'set'
  ed    it
  cat/dog
  33.2
  D

set работы чего-либо, что следует за ним, для создания 1$, 2$ и т.д. Это, конечно, переопределит исходные значения, поэтому просто знать об этом.

1
27.01.2020, 19:57
  • 1
    ahh... так 'оценкой' Вы имели в виду косвенную ссылку... $ {! var} конструкция более безопасна, как то, что я записал в своем ответе –  pepoluan 17.04.2011, 11:51
  • 2
    @pepoluan... Спасибо за предупреждение меня к этому. Намного более просто записать... (Я сейчас вернулся к веб-странице, которую я упомянул, Если бы я читал далее, то я видел бы, что она упомянула это там также :(.... –  Peter.O 19.04.2011, 13:27
  • 3
    heh., но если косвенность происходит на левой стороне, оценка, является необходимым злом, хотя :) –  pepoluan 19.04.2011, 14:45
  • 4
    @peopluan... хорошо, благодарит указать на это... и так же, как в стороне: Я не понимаю почему eval как полагают, некоторыми, evil ... (возможно, это из-за написания :)... Если eval "плохо", затем $ {! var} одинаково "плохо"?... Мне это - просто часть языка и полезная часть, в этом.. но я определенно предпочитаю $ {! var}... –  Peter.O 27.04.2011, 05:03

Обратите внимание, что я поддерживаю пробелы в именах файлов.

function SplitFilePath {
    IFS=$'/' eval "${1}"=\( \${2} \)
}
function JoinFilePath {
    IFS=$'/' eval echo -n \"\${*}\"
    [ $# -eq 1 -a "${1}" = "" ] && echo -n "/"
}
function path_common {
    set -- "${@//\/\///}"       ## Replace all '//' with '/'
    local -a Path1
    local -i Cnt=0
    SplitFilePath Path1 "${1}"
    IFS=$'/' eval set -- \${2} 
    for CName in "${Path1[@]}" ; do
        [ "${CName}" != "${1}" ] && break;
        shift && (( Cnt++ ))
    done
    JoinFilePath "${Path1[@]:0:${Cnt}}"
}

Я добавил тестовый сценарий для имен файлов с пробелами и зафиксировал 2 теста, которые пропускали продвижение /

    do_test () {

  if test "${@}"; then echo 0; else echo $? "$@"; failed=$(($failed+1)); fi
}

run_tests () {
  function_to_test=$1; shift
  failed=0
  do_test "$($function_to_test /a/b/c/d /a/b/e/f; echo x)" = /a/bx
  do_test "$($function_to_test /long/names/foo /long/names/bar; echo x)" = /long/namesx
  do_test "$($function_to_test / /a/b/c; echo x)" = /x      
  do_test "$($function_to_test a/b/c/d a/b/e/f ; echo x)" = a/bx
  do_test "$($function_to_test ./a/b/c/d ./a/b/e/f; echo x)" = ./a/bx
  do_test "$($function_to_test '
/
/
' '
/
'; echo x)" = '
/
'x
  do_test "$($function_to_test --/-- --; echo x)" = '--x'
  do_test "$($function_to_test '' ''; echo x)" = x
  do_test "$($function_to_test /foo/bar ''; echo x)" = x
  do_test "$($function_to_test /foo /fo; echo x)" = /x      ## Changed from x
  do_test "$($function_to_test '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
' '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
'; echo x)" = '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
'x
  do_test "$($function_to_test /foo/bar //foo//bar//baz; echo x)" = /foo/barx
  do_test "$($function_to_test foo foo; echo x)" = foox
  do_test "$($function_to_test /fo /foo; echo x)" = /x          ## Changed from x
  do_test "$($function_to_test "/fo d/fo" "/fo d/foo"; echo x)" = "/fo dx"

  if [ $failed -ne 0 ]; then echo $failed failures; return 1; fi
}
1
27.01.2020, 19:57

Теги

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