Extrahieren Sie jedes n-te Zeichen aus einer Zeichenfolge

Extrahieren Sie jedes n-te Zeichen aus einer Zeichenfolge

Ich versuche eine Lösung fürDasFrage. Meine bisherige Herangehensweise an dieses Problem ist wie folgt.

  • Hängen Sie alle Zeichen aneinander, um eine lange Zeichenfolge zu erstellen.
  • Entfernen Sie nach dem obigen Schritt alle Leerzeichen oder Tabulatoren, sodass wir nur eine große Zeichenfolge haben.

Ich konnte die oben genannten Schritte mit dem folgenden Befehl durchführen.

column -s '\t' inputfile | tr -d '[:space:]'

Für eine Eingabedatei wie diese,

1   0   0   0   0   0

0   1   1   1   0   0

Nach dem Anwenden des obigen Befehls habe ich die Werte als:

100000011100

Jetzt versuche ich, in diesem großen String den folgenden Ansatz anzuwenden.

Extrahieren Sie jedes 6. Zeichen (wie es der ursprüngliche OP möchte) und hängen Sie es bis zum Ende der Zeichenfolge an ein Array-Element an.

Im Grunde versuche ich mit dem obigen Schritt, die Array-Elemente wie folgt zu erstellen:

10(1. und 7. Zeichen ), 01(2. und 8. Zeichen ), 01(3. und 9. Zeichen ), 01(4. und 10. Zeichen ), 00(5. und 11. Zeichen ), 00(6. und 12. Zeichen ).

Meine Frage ist also: Wie kann ich jedes n- te Zeichen extrahieren, damit ich es einem Array hinzufügen und weitermachen kann? (in diesem Fall n=6).

Antwort1

Zwei Linien

Hier ist eine reine bashLösung, die ein Array erzeugt bash:

s="100000011100"
array=($(
    for ((i=0; i<${#s}-6; i++))
    do
        echo "${s:$i:1}${s:$((i+6)):1}"
    done
    ))
echo "${array[@]}"

Dies führt zu derselben Ausgabe wie in der Frage:

10 01 01 01 00 00

Das Schlüsselelement hierbei ist die Verwendung von BashsTeilstringerweiterung. Bash ermöglicht die Extraktion von Teilzeichenfolgen aus einer Variablen, beispielsweise parameterüber ${parameter:offset:length}. In unserem Fall wird der Offset durch die Schleifenvariable bestimmt iund die Länge beträgt immer 1.

Allgemeine Lösung für eine beliebige Anzahl von Zeilen

Nehmen wir beispielsweise an, dass unsere ursprüngliche Zeichenfolge 18 Zeichen hat und wir das i-te, das i+6-te und das i+12-te Zeichen für i von 0 bis 5 extrahieren möchten. Dann gilt:

s="100000011100234567"
array=($(
    for ((i=0; i<6; i++))
    do
        new=${s:$i:1}
        for ((j=i+6; j<${#s}; j=j+6))
        do 
            new="$new${s:$j:1}"
        done
        echo "$new"
    done
    ))

echo "${array[@]}"

Das Ergebnis ist:

102 013 014 015 006 007

Derselbe Code kann auf eine beliebige Anzahl von 6-stelligen Zeilen erweitert werden. Er besteht beispielsweise aus sdrei Zeilen (18 Zeichen):

s="100000011100234567abcdef"

Die Ausgabe lautet dann:

102a 013b 014c 015d 006e 007f

Antwort2

Verwendung von perl:

$ echo 100000011100 | perl -nle '
    for ($i = 0; $i < length()/2; $i++) {
        print substr($_,$i,1), substr($_,$i+6,1);
    }
'
10
01
01
01
00
00

Es funktioniert für zwei Zeilen. Wenn Sie mit einer beliebigen Anzahl von Zeilen arbeiten möchten, sollten Sie die Zeilen direkt verarbeiten, anstatt große Zeichenfolgen zu erstellen. Mit dieser Eingabe:

1   0   0   0   0   0                                                           
0   1   1   1   0   0                                                           
0   0   0   0   0   0

Versuchen:

$ perl -anle '
    for ($i = 0; $i <= $#F; $i++) {
      push @{$h{$i}}, $F[$i];
    }
    END {
        print @{$h{$_}} for keys %h;
    }
' file
000
010
000
100
010
010

Antwort3

Als Shell-Lösung getoptsist es wahrscheinlich am einfachsten. Die Sache getoptsist, dass es POSIX-spezifiziert ist, genau das zu tun, was Sie verlangen – einen Byte-Stream in einer Shell-Schleife zu verarbeiten. Ich weiß, das klingt komisch, denn wenn Sie wie ich sind, bevor ich das gelernt habe, denken Sie wahrscheinlich:also, ich dachte, es sollte Befehlszeilenschalter verarbeiten.Das stimmt, aber das Erste trifft auch zu. Bedenken Sie:

-thisisonelongstringconsistingofseparatecommandlineswitches

Ja, getoptsdas muss man handhaben. Es muss das in einer Schleife Zeichen für Zeichen aufteilen und jedes Zeichen entweder in der Shell-Variable $OPTARGoder in einer anderen, die Sie namentlich angeben, an Sie zurückgeben, je nachdem, wie spezifisch Sie es beim Aufrufen machen. Darüber hinaus muss es Fehler in Shell-Variablen zurückgeben undseinen Fortschritt speichernwenn dies in der Shell-Variable der Fall ist, $OPTINDkann esgenau dort fortfahren, wo es aufgehört hatwenn Sie es irgendwie ansprechen können. Und es muss die gesamte Aufgabe erledigen, ohne eine einzige Subshell aufzurufen.

Nehmen wir also an, wir haben:

arg=$(seq -s '' 1000); set --
while getopts :0123456789 v -"${arg}"
do [ "$((i=$i+1<6?$i+1:0))" -gt 0 ] ||
set "$@" "$v"
done

Hmmm... ich frage mich, ob es funktioniert hat?

echo "$((${#arg}/6))" "$#"
482 482

Das ist schön...

eval '
printf %.1s\\n "${arg#'"$(printf %0$((124*6-1))d | tr 0 \?)"'}" "${124}"'
4
4

Wie Sie also sehen, getoptslegt der Befehl das Array für jedes sechste Byte in der Zeichenfolge vollständig fest. Und es müssen nicht unbedingt Zahlen wie diese sein – noch müssen es Shell-sichere Zeichen sein – und Sie müssen nicht einmal die Zielzeichen angeben, wie ich es oben bei 01234565789beiden getan habe. Ich habe dies wiederholt in vielen Shells getestet und sie funktionieren alle einfach. Es gibt einige Eigenheiten – es bashwirft das erste Zeichen weg, wenn es ein Leerzeichen ist – dashakzeptiert den :Doppelpunkt als angegebenen Parameter, obwohl dies so ziemlich das einzige ist, was POSIX ausdrücklich verbietet. Aber nichts davon ist wichtig, da getoptsder Wert des aktuellen opt-Zeichens trotzdem hinterlegt wird, $OPTARGselbst wenn es Ihnen einen Fehler zurückgibt(dargestellt durch ein ?, das Ihrer angegebenen opt-Variable zugewiesen ist)und ansonsten explizit aufgehoben, $OPTARGes sei denn, Sie haben deklariert, dass eine Option ein Argument haben sollte. Und die Sache mit den Leerzeichen ist eigentlich eine gute Sache - es verwirft nurführendPlatz, was hervorragend ist, denn wenn Sie mit unbekannten Werten arbeiten, können Sie Folgendes tun:

getopts : o -" $unknown_value"

... um die Schleife zu starten, ohne dass die Gefahr besteht, dass das erste Zeichen tatsächlich in Ihrer akzeptierten Argumentzeichenfolge enthalten ist – was dazu führen würde, dass getoptsdas Ganze auf $OPTARGeinmal als Argument eingefügt wird.

Hier ist ein weiteres Beispiel:

OPTIND=1
while getopts : o -" $(dd if=/dev/urandom bs=16 count=1 2>/dev/null)"                         
do printf '\\%04o' "'$OPTARG"; done  

\0040\0150\0071\0365\0320\0070\0161\0064\0274\0115\0012\0215\0222\0271\0146\0057\0166

Ich habe es $OPTIND=1in der ersten Zeile eingestellt, weil ich es gerade verwendet habe getoptsund bis Sie es zurücksetzen, erwartet es, dass sein nächster Aufruf dort fortgesetzt wird, wo es aufgehört hat – es will es "${arg2}"mit anderen Worten. Aber ich habe keine Lust, nachzugeben, und mache jetzt etwas anderes, also lasse ich es durch Zurücksetzen wissen, $OPTINDan welchem ​​Punkt es bereit ist, weiterzumachen.

In diesem Beispiel habe ich zsh- was kein Problem mit einem führenden Leerzeichen ist - verwendet, sodass das erste Zeichen Oktal 40 ist - das Leerzeichen. Normalerweise verwende ich es getoptsjedoch nicht auf diese Weise - ich verwende es normalerweise, umvermeidenmache write()für jedes Byte ein und weise stattdessen seine Ausgabe – die in einer Variable kommt – einer anderen Shell-Variable zu – wie ich es oben mit „ setafter a fashion“ gemacht habe. Wenn ich dann fertig bin, kann ich die ganze Zeichenfolge nehmen und wenn ich das tue, entferne ich normalerweise das erste Byte.

Antwort4

sedist das Erste, was mir in den Sinn kommt.

$ echo 1234567890abcdefghijklmnopqrstuvwxyz | sed 's/.\{5\}\(.\)/\1/g'
6bhntz

Bringen Sie 5 Charaktere in Einklang, erfassen Sie den 6. und ersetzen Sie sie alle durch den erfassten Charakter.

Dies führt jedoch zu einem Problem, wenn die Länge der Zeichenfolge kein genaues Vielfaches von 6 ist:

$ echo 1234567890abcdefghijklmnopqrstuvwxy | sed 's/.\{5\}\(.\)/\1/g' 
6bhntuvwxy

Dies können wir jedoch beheben, indem wir Folgendes sedein wenig ändern:

$ echo 1234567890abcdefghijklmnopqrstuvwxy | sed 's/.\{1,5\}\(.\{0,1\}\)/\1/g'
6bhnt

Aufgrund der gierigen Natur regulärer Ausdrücke werden bei den Übereinstimmungen mit variabler Länge so viele Übereinstimmungen wie möglich gefunden. Wenn nichts zum Erfassen übrig bleibt, wird es nicht erfasst und die Zeichen werden einfach gelöscht.

verwandte Informationen