Indizieren und Ändern des Bash-Parameterarrays $ @

11

Ist es möglich, auf Indizes in zu verweisen $@? Ich kann nirgendwo im GrayCat-Wiki einen Verweis finden, der wie folgt verwendet werden kann , und das Advanced Scripting Guide und andere weisen dies einer anderen Variablen zu, bevor sie stattdessen geändert werden.

$ echo ${@[0]}
-bash: ${@[0]}: bad substitution

Das Ziel ist DRY : Das erste Argument wird für eine Sache und der Rest für etwas anderes verwendet, und ich möchte vermeiden, entweder den zu normalisierenden Code $@oder das Array zu duplizieren oder eine separate Funktion dafür zu erstellen (obwohl dies der Fall ist) Punkt, es ist wahrscheinlich der einfachste Ausweg).

Erläuterung: Ziel war es, die Werte der variablen Länge zu ändern $@, um das Debuggen des Codes zu vereinfachen. Die aktuelle Version ist etwas zu hackig für meinen Geschmack, obwohl sie auch für bizarre Pfade wie funktioniert

$'--$`\! *@ \a\b\e\E\f\r\t\v\\\"\' \n'

Update : Sieht so aus, als wäre das nicht möglich. Der Code verwendet jetzt sowohl Code- als auch Datenvervielfältigung, funktioniert aber zumindest:

path_common()
{
    # Get the deepest common path.
    local common_path="$(echo -n "${1:-}x" | tr -s '/')"
    common_path="${common_path%x}"
    shift # $1 is obviously part of $1
    local path

    while [ -n "${1+defined}" ]
    do
        path="$(echo -n "${1}x" | tr -s '/')"
        path="${path%x}"
        if [[ "${path%/}/" = "${common_path%/}/"* ]]
        then
            shift
        else
            new_common_path="${common_path%/*}"
            [ "$new_common_path" = "$common_path" ] && return 1 # Dead end
            common_path="$new_common_path"
        fi
    done
    printf %s "$common_path"
}

Bounty geht an alle , die von der loswerden kann Duplizierung von Code doppelte Schrägstriche oder kollabieren Duplizierung von Daten zu halten $1und die anderen Parameter, oder beide, während der Code eine vernünftige Größe zu halten und alle die Unit - Tests Erfolg:

test "$(path_common /a/b/c/d /a/b/e/f; echo x)" = /a/bx
test "$(path_common /long/names/foo /long/names/bar; echo x)" = /long/namesx
test "$(path_common / /a/b/c; echo x)" = /x
test "$(path_common a/b/c/d a/b/e/f ; echo x)" = a/bx
test "$(path_common ./a/b/c/d ./a/b/e/f; echo x)" = ./a/bx
test "$(path_common $'\n/\n/\n' $'\n/\n'; echo x)" = $'\n/\n'x
test "$(path_common --/-- --; echo x)" = '--x'
test "$(path_common '' ''; echo x)" = x
test "$(path_common /foo/bar ''; echo x)" = x
test "$(path_common /foo /fo; echo x)" = x
test "$(path_common $'--$`\! *@ \a\b\e\E\f\r\t\v\\\"\' \n' $'--$`\! *@ \a\b\e\E\f\r\t\v\\\"\' \n'; echo x)" = $'--$`\! *@ \a\b\e\E\f\r\t\v\\\"\' \n'x
test "$(path_common /foo/bar //foo//bar//baz; echo x)" = /foo/barx
test "$(path_common foo foo; echo x)" = foox
test "$(path_common /fo /foo; echo x)" = x
l0b0
quelle
Verwandte: stackoverflow.com/questions/4827690/…
Ciro Santilli 事件 改造 中心 法轮功 六四 事件

Antworten:

16

POSIX

Um die Schrägstriche in allen Parametern zu normalisieren, verwende ich den Trick mit dem rotierenden Argument: Verschieben $1, transformieren und das Ergebnis am Ende der Parameterliste einfügen. Wenn Sie dies so oft tun, wie Parameter vorhanden sind, haben Sie alle Parameter transformiert und wieder in Ordnung gebracht.

Für den zweiten Teil des Codes habe ich Ihre Logik so geändert, dass sie weniger verwirrend ist: Die äußere Schleife durchläuft die Parameter und die innere Schleife durchläuft die Pfadkomponenten. for x; do … doneiteriert über die Positionsparameter, es ist eine bequeme Redewendung. Ich verwende eine POSIX-kompatible Methode, um eine Zeichenfolge mit einem Muster abzugleichen: dem caseKonstrukt.

Getestet mit Strich 0.5.5.1, pdksh 5.2.14, bash 3.2.39, bash 4.1.5, ksh 93s +, zsh 4.3.10.

Randnotiz: Es scheint einen Fehler in Bash 4.1.5 zu geben (nicht in 3.2): Wenn das Fallmuster ist "${common_path%/}"/*, schlägt einer der Tests fehl.

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"
}

Bash, ksh

Wenn Sie in Bash (oder Ksh) sind, können Sie Arrays verwenden - ich verstehe nicht, warum Sie sich anscheinend auf die Positionsparameter beschränken. Hier ist eine Version, die ein Array verwendet. Ich muss zugeben, dass es nicht besonders klarer ist als die POSIX-Version, aber es vermeidet das anfängliche Mischen von n ^ 2.

Für den Slash-Normalisierungsteil verwende ich das Konstrukt ksh93- ${foo//PATTERN/REPLACEMENT}Konstrukt, um alle Vorkommen von PATTERNin $fooby zu ersetzen REPLACEMENT. Das Muster muss +(\/)mit einem oder mehreren Schrägstrichen übereinstimmen. unter Bash shopt -s extglobmuss in Kraft sein (äquivalent, Bash mit beginnen bash -O extglob). Das Konstrukt set ${!a[@]}setzt die Positionsparameter auf die Liste der Indizes des Arrays a. Dies bietet eine bequeme Möglichkeit, die Elemente des Arrays zu durchlaufen.

Für den zweiten Teil habe ich die gleiche Schleifenlogik wie die POSIX-Version. Dieses Mal kann ich es verwenden, [[ … ]]da alle hier anvisierten Shells es unterstützen.

Getestet mit Bash 3.2.39, Bash 4.1.5, ksh 93s +.

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

Leider fehlt zsh die ${!array[@]}Funktion, um die ksh93-Version unverändert auszuführen. Glücklicherweise hat zsh zwei Funktionen, die den ersten Teil zum Kinderspiel machen. Sie können die Positionsparameter so indizieren, als wären sie das @Array, sodass kein Zwischenarray verwendet werden muss. Und zsh hat ein Array-Iterationskonstrukt : "${(@)array//PATTERN/REPLACEMENT}"Führt nacheinander die Musterersetzung für jedes Array-Element durch und wertet das Ergebnisarray aus (verwirrenderweise benötigen Sie doppelte Anführungszeichen, obwohl das Ergebnis mehrere Wörter enthält; dies ist eine Verallgemeinerung von "$@"). Der zweite Teil ist im Wesentlichen unverändert.

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"
}

Testfälle

Meine Lösungen werden nur minimal getestet und kommentiert. Ich habe die Syntax Ihrer Testfälle geändert, um sie unter Shells zu analysieren, die keine $'…'Fehler haben, und Fehler auf bequemere Weise zu melden.

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
}
Gilles 'SO - hör auf böse zu sein'
quelle
1
+50, nur wow. Jedenfalls mehr als ich verlangt hatte. Sie, Sir, sind großartig.
10b0
In der POSIX-Diskussion, in der ersten Schleife, in der Sie Schrägstriche normalisieren, warum "." mit dem sprintf dann in der nächsten zeile ausziehen? Der Code scheint ohne ihn zu funktionieren, aber ich vermute, Sie bearbeiten einen Randfall, den ich nicht kenne.
Alan De Smet
1
@AlanDeSmet Der Randfall ist, wenn die Zeichenfolge mit einer neuen Zeile endet. Durch das Ersetzen von Befehlen werden nachfolgende Zeilenumbrüche entfernt.
Gilles 'SO - hör auf böse zu sein'
6

Warum benutzt du nicht einfach $ 1, $ 2 .. $ 9, $ {10}, $ {11} .. und so weiter? Es ist noch trockener als das, was du versuchst :)

Mehr zur Beziehung zwischen $ number und $ @:

$ @ kann als Abkürzung für "alle Elemente eines Arrays mit allen Argumenten" betrachtet werden.

$ @ Ist also eine Art Abkürzung von $ {args [@]} (args hier ist ein 'virtuelles' Array, das alle Argumente enthält - wohlgemerkt keine echte Variable)

$ 1 ist $ {args [1]}, $ 2 ist $ {args [2]} und so weiter.

Wenn Sie [9] gedrückt haben, verwenden Sie eine geschweifte Klammer: $ {10} ist $ {args [10]}, $ {11} ist $ {args [11]} und so weiter.


Verwenden Sie indirekt ein Befehlszeilenargument

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

Beispiel:

argc=$#
for (( argn=1; argn<=argc; argn++)); do
    if [[ ${!argn} == "foo" ]]; then
        echo "Argument $argn of $argc is 'foo'"
    fi
done
pepoluan
quelle
Der offensichtliche Nachteil der Verwendung von $ * number * ist, dass Sie keine Indexvariable wie bei verwenden können ${args[$i]}.
Intuitiert
@intuited dann Indirektion verwenden; Ich werde meine Antwort bearbeiten.
Pepoluan
5

Das erste Argument wird für eine Sache verwendet, der Rest für etwas anderes.

Ich denke was du willst ist 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
forcefsck
quelle
1

Ich weiß nicht genau, warum Sie nicht nur $ 1 $ 2 usw. verwenden, sondern .. Dies kann Ihren Bedürfnissen entsprechen.

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

Ausgabe

  -------- 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 funktioniert von allem, was darauf folgt, um $ 1, $ 2 .. etc .. zu erstellen. Dies wird natürlich die ursprünglichen Werte überschreiben, also sei dir dessen einfach bewusst.

Peter.O
quelle
ahh ... also mit 'eval' meintest du indirekte Referenz ... $ {! var} Konstrukt ist sicherer, wie ich es in meiner Antwort geschrieben habe
pepoluan
@pepoluan ... Danke, dass du mich darauf aufmerksam gemacht hast. Es ist viel einfacher zu schreiben ... (Ich bin gerade zu der Webseite zurückgekehrt, auf die ich verwiesen habe. Wenn ich weiter gelesen hätte, hätte ich gesehen, dass es dort auch erwähnt wurde :( ....
Peter.O
heh. aber wenn die Indirektion auf der linken Seite geschieht , ist eval ein notwendiges Übel, tho ':)
pepoluan
@peopluan ... okay, danke, dass du darauf hingewiesen hast ... und nur zur Seite: Ich verstehe nicht, warum evalmanche es für evil... halten (vielleicht liegt es an der Schreibweise :) ... Wenn eval"schlecht" ist, ist $ {! Var} dann gleichermaßen "schlecht"? ... Für mich ist es nur ein Teil der Sprache und ein nützlicher Teil dabei ... aber ich bevorzuge definitiv $ {! Var} ...
Peter.O
1

Hinweis Ich unterstütze Leerzeichen in Dateinamen.

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}}"
}

Ich habe einen Testfall für Dateinamen mit Leerzeichen hinzugefügt und 2 Tests behoben, bei denen ein führendes / fehlte

    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
}
John Kearney
quelle