Fehler in der Shell-Funktion, um gerade Zahlen zu zählen


12

Für eine Aufgabe muss ich eine Funktion schreiben, die die Anzahl der geraden Zahlen druckt, wenn sie mit einer Folge von Zahlen versehen ist.

Ich habe den Code verwendet, den ich für eine vorherige Aufgabe verwendet habe (um zu drucken, 1wann eine Zahl gerade und 0wann die Zahl ungerade war)

Mein Problem ist jetzt, dass meine Funktion weiter druckt 0. Was mache ich falsch?

Hier ist mein Drehbuch:

#!/usr/bin/bash
# File: nevens.sh

# Write a function called nevens which prints the number of even numbers when provided with a sequence of numbers.
# Check: input nevens 42 6 7 9 33 = output 2

function nevens {

        local sum=0

        for element in $@
        do
                let evencheck=$(( $# % 2 ))
                if [[ $evencheck -eq 0 ]]
                then
                        let sum=$sum+1
                fi
        done

        echo $sum
}

2
schreibe in deinen shebang '#! / usr / bin / bash -x' dann siehst du was genau passiert.
Ziazis

+1, weil Sie uns mitgeteilt haben, dass es sich um Hausaufgaben handelt - und weil Sie hart genug daran gearbeitet haben, um Hilfe zu verdienen.
Joe

Antworten:


20

Sie haben nur vergessen, in der Schleife durch $#( $) zu ersetzen :elementfor

function nevens {
  local sum=0
  for element in $@; do
    let evencheck=$(( element % 2 ))
    if [[ $evencheck -eq 0 ]]; then
      let sum=sum+1
    fi
  done
  echo $sum
}

Testen Sie nun die Funktion:

$ nevens 42 6 7 9 33
2
$ nevens 42 6 7 9 33 22
3
$ nevens {1..10..2} # 1 to 10 step 2 → odd numbers only
0
$ nevens {2..10..2} # 2 to 10 step 2 → five even numbers
5

17

@dessert hat das Kernproblem gefunden, ich gebe eine Codeüberprüfung :

  1. The shebang: /usr/bin/bashIn Ubuntu gibt es keine . Es ist /bin/bash.
  2. Es ist gut, dass Sie sum localden Variablennamensraum außerhalb der Funktion deklariert und nicht verschmutzt haben. Darüber hinaus können Sie es mit der folgenden -iOption als Ganzzahlvariable deklarieren :

    local -i sum=0
  3. Zitieren Sie immer Ihre Variablen (und Parameter)! In diesem Skript ist es nicht erforderlich, aber es ist eine sehr gute Angewohnheit, sich darauf einzulassen:

    for element in "$@"
    do

    Das heißt, Sie können das in "$@"hier weglassen :

    for element
    do

    Wenn dies in <something>nicht angegeben ist, fordurchläuft die Schleife implizit die Argumente. Dies kann Fehler wie das Vergessen der Anführungszeichen vermeiden.

  4. Es ist nicht erforderlich, das Ergebnis zu berechnen und anschließend zu überprüfen. Sie können die Berechnung direkt in den folgenden Schritten durchführen if:

    if (( (element % 2) == 0 ))
    then
        ((sum = sum + 1))
    fi

    (( ... ))ist der arithmetische Kontext . Dies ist nützlicher als die [[ ... ]]Durchführung von arithmetischen Prüfungen. Außerdem können Sie die $Vor-Variablen weglassen (was das Lesen erleichtert, IMHO).

  5. Wenn Sie den Teil mit der geraden Prüfung in eine separate Funktion verschoben haben, kann dies die Lesbarkeit und Wiederverwendbarkeit verbessern:

    function evencheck
    {
        return $(( $1 % 2 ))
    }
    function nevens
    {
        local -i sum=0
        for element
        do
            # `if` implicitly checks that the returned value/exit status is 0
            if evencheck "$element"
            then
                (( sum++ ))
            fi
        done
        echo "$sum"
    }

1
Wenn Sie auf der Suche nach einer Terser-Loop-Karosserie sind, verwenden Sie diese sum=$((sum + 1 - element % 2)).
David Foerster

1
@ DavidFoerster Terser und weniger lesbar. ;-)
Konrad Rudolph

@DavidFoerster: Ich hatte den gleichen Gedanken, dass man einfach das Mod-2-Ergebnis direkt verwenden sollte. (Oder besser, &1um das niedrige Bit zu überprüfen, ob es für Sie besser lesbar ist). Aber wir können es lesbarer machen: sum=$((sum + !(element&1) ))Verwenden Sie stattdessen eine Boolesche Inverse +1 - condition. Oder zählen Sie einfach die ungeraden Elemente mit ((odd += element&1))und drucken Sie am Ende mit echo $(($# - element)), weil even = total - odd.
Peter Cordes

Gute Arbeit und gute Hinweise auf Kleinigkeiten, die Anfänger vermissen, z . B. local -iund sum++.
Paddy Landau

4

Ich bin mir nicht sicher, ob Sie für andere Lösungen offen sind. Ich weiß auch nicht, ob Sie externe Dienstprogramme verwenden können oder ob Sie sich nur auf Bash-Builtins beschränken. Wenn Sie grepzum Beispiel verwenden können, könnte Ihre Funktion viel einfacher sein:

function nevens {
    printf "%s\n" "$@" | grep -c '[02468]$'
}

Dadurch wird jede Eingabe-Ganzzahl in eine eigene Zeile gesetzt und anschließend grepdie Zeilen gezählt, die mit einer geraden Ziffer enden.


Update - @PeterCordes wies darauf hin, dass wir dies sogar ohne grep tun können - nur reine Bash, solange die Eingabeliste nur gut geformte Ganzzahlen enthält (ohne Dezimalstellen):

function nevens{
    evens=( ${@/%*[13579]/} )
    echo "${#evens[@]}"
}

Dies funktioniert, indem Sie eine Liste erstellen, die aufgerufen wird, evensindem Sie alle Quoten herausfiltern und dann die Länge dieser Liste zurückgeben.


Interessanter Ansatz. Natürlich zählt dies 3.0als gerade Zahl; Die Frage war nicht genau, wie die Zahlen aussehen würden.
G-Man sagt, dass Monica am

@ G-Man, da bash keine Gleitkomma-Arithmetik unterstützt, ist es sicher, ganze Zahlen anzunehmen
muru

Da in der Frage „gerade“ (und implizit „ungerade“) Zahlen genannt werden, kann man mit einiger Sicherheit davon ausgehen, dass es sich um Ganzzahlen handelt. Ich verstehe nicht, wie gut es ist, aus den Fähigkeiten und Einschränkungen des Tools, das der Benutzer voraussichtlich verwenden wird, Schlussfolgerungen über die Frage zu ziehen. Denken Sie daran: Die Frage besagt, dass dies eine Aufgabe ist - ich denke, für die Schule, weil dies viel zu trivial ist, um von einem Job zu sein. Schullehrer kommen mit einigen verrückten Dingen auf.
G-Man sagt, dass Monica am

2
Bash kann eine Liste selbst filtern, wenn wir annehmen, dass keine Elemente Leerzeichen enthalten: Erweitern Sie die Argumentliste mit ungeraden Zahlen, die durch die leere Zeichenfolge ersetzt werden, indem Sie ${/%/}im @Array eine Übereinstimmung am Ende der Zeichenfolge innerhalb eines Array-Initialisierers angeben. Zählung drucken. Als One-liner , sie zu definieren und auszuführen: foo(){ evens=( ${@/%*[13579]/} ); echo "${#evens[@]} even numbers"; printf "%s\n" "${evens[@]}"; }; foo 135 212 325 3 6 3 4 5 9 7 2 12310. Beinhaltet das tatsächliche Drucken der Liste zum Debuggen. Abzüge 5 even numbers 212 6 4 2 12310(in sep. Zeilen)
Peter Cordes

1
Wahrscheinlich hat ZSH etwas, um eine Liste richtig zu filtern, anstatt abhängig von der Wortteilung ein neues Array zu erstellen (ich war ein bisschen überrascht, dass Bash es nicht tat; wende nur skalare Zeichenkettenerweiterungen auf jedes Element an). Bei großen Listen würde es mich nicht wundern, wenn greptatsächlich schneller wäre als bash. Hmm, ich frage mich, ob es eine Codegolf-Frage gibt, wo ich diese Bash-Funktion posten könnte: P
Peter Cordes
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.