staging.inyokaproject.org

take und drop in der Shell

Status: Ungelöst | Ubuntu-Version: Xubuntu 20.04 (Focal Fossa)
Antworten |

user_unknown

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17432

Angeregt von Collections-Funktionen in Scala suchte ich in der Bash etwas, das ohne Sonderzeichen Argumente nach Position aus der Pipeline auswählt.

Bahnhof?

Ok, ein Beispielblock:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
t530:~/proj/mini/forum 🐧> uname -a 
Linux t530 5.4.0-88-generic #99-Ubuntu SMP Thu Sep 23 17:29:00 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
t530:~/proj/mini/forum 🐧> uname -a | take 5 
Linux t530 5.4.0-88-generic #99-Ubuntu SMP 
t530:~/proj/mini/forum 🐧> uname -a | drop 5 
Thu Sep 23 17:29:00 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux 
t530:~/proj/mini/forum 🐧> uname -a | drop 5 | take 6
Thu Sep 23 17:29:00 UTC 2021 
t530:~/proj/mini/forum 🐧> uname -a | take 11 | drop 5 
Thu Sep 23 17:29:00 UTC 2021 

Ich weiß, der klassische Weg ist hier awk:

1
uname -a | awk '{ print $6" "$7" "$8" "$9" "$10" "$11 }'

Auch dachte ich mal, einen Weg gesehen zu haben, mit Syntax einen Arraybereich auswählen zu können, also in Pseudocode sowas zu schreiben:

1
2
arr=($(uname -a))
echo ${arr[{5..11}]}

aber mich trügt wohl die Erinnerung, oder ich habe es nirgends gefunden.

Praktisches Einsatzgebiet sind geschwätzige Programme, die keinen Schalter haben, um Überschriften etc. auszuschalten:

1
2
wc -l demo.sh 
32 demo.sh

wobei wc nicht das beste Beispiel ist, da man es mit cat überlisten kann:

1
2
cat demo.sh | wc -l 
32

Hier lässt sich dann das Ergebnis einfach auffangen und damit weiterarbeiten.

Die Scripte take.sh und drop.sh sind nicht sehr elegant oder optimiert, aber tun was sie sollen. Einen Schalter, um den Delimiter festzulegen haben sie nicht, keine manpage, keine --version info. Gedacht sind sie als Shellfunktionen, die man in der .bashrc o.ä. sourced. take.sh:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/bin/bash

take ()
{
    read zeile;
    arr=($zeile);
    for ((i=0; i < $1; i+=1))
    do
        echo -n "${arr[i]} ";
    done;
    echo
}

Hier das vergleichbar monströse drop, aber das liegt daran, dass es einen Kommentar enthält, der sich weitgehend mit diesem Posting deckt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/bin/bash
#
# Problemlage:
#   zwei Versionen des Programms syntrax liegen vor:
#
# > whereis syntrax
# > syntrax: /usr/local/bin/syntrax /home/stefan/bin/syntrax
#
# Man würde nun gerne mit (tk)diff die Unterschiede sehen - dazu müsste man das störende, erste Wort "syntrax:" loswerden
# whereis bietet selbst leider keinen Schalter an.
# Die typische Lösung i.d. Linuxshell sieht so aus:
#
# > tkdiff $(whereis syntrax | awk '{print $2, $3}')
#
# Geschweifte Klammern, Apostrophe, Fingergymnastik. Wieos gibt es kein drop?
#
# Ziel ist es, tippen zu können:
#
# tkdiff $(whereis syntrax | drop 1)
#
# Here we go. This program is licensed under the GPLv3
#
drop () {
        read zeile
        arr=($zeile)
        for ((i=$1; i < ${#arr[@]}; i+=1))
        do
                echo -n ${arr[i]}" "
        done
        echo
}

# todo: check, whether called with param. If so, execute, else probably a sourcing
(( $# == 1 )) && drop $1

Beides ist GPLv3. Im ersten könnte ich auch noch etwas wie einen Programmaufruf für das Argument, so eins da ist, ergänzen. Aber im Moment bin ich sehr beschäftigt.

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 10978

Reicht da normalerweise nicht cut? Also z.B. für deine Beispiele mit uname:

$ uname -a
Linux ikarus 5.11.2-1-ARCH #1 SMP PREEMPT Sun Feb 28 22:03:25 UTC 2021 armv7l GNU/Linux
$ uname -a | cut -d ' ' -f 1-5
Linux ikarus 5.11.2-1-ARCH #1 SMP
$ uname -a | cut -d ' ' -f 7-12
Sun Feb 28 22:03:25 UTC 2021 

Array-Slicing wäre auch eine Möglichkeit (gemäß https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion):

1
2
3
4
5
6
slice () {
  while read -ra line
  do
     echo "${line[@]:$1:$2}" # $1 = index, $2 = length of the slice
  done
}
$ uname -a | slice 0 5
Linux ikarus 5.11.2-1-ARCH #1 SMP
$ uname -a | supercut 6 6
Sun Feb 28 22:03:25 UTC 2021 

user_unknown

(Themenstarter)
Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17432

seahawk1986 schrieb:

Reicht da normalerweise nicht cut?

Doch, absolut.

Abgesehen davon, dass ich es dann doch so selten nutze, dass ich immer wieder vergessen haben, dass man dann mit -f auf die Feldliste zugreift und ich daher wohl mit cut nicht so warm geworden bin, wie es das verdient hätte.

Also z.B. für deine Beispiele mit uname:

$ uname -a
Linux ikarus 5.11.2-1-ARCH #1 SMP PREEMPT Sun Feb 28 22:03:25 UTC 2021 armv7l GNU/Linux
$ uname -a | cut -d ' ' -f 1-5
Linux ikarus 5.11.2-1-ARCH #1 SMP
$ uname -a | cut -d ' ' -f 7-12
Sun Feb 28 22:03:25 UTC 2021 

Ja, eigentlich simpel genug, sich das einzuprägen, statt sich Kommandos anzugewöhnen, die es nur auf localhost gibt.

Array-Slicing wäre auch eine Möglichkeit (gemäß https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion):

1
2
3
4
5
6
slice () {
  while read -ra line
  do
     echo "${line[@]:$1:$2}" # $1 = index, $2 = length of the slice
  done
}

Siehste! Hat sich doch gelohnt das Posting. Dass ich was sinnvolles zur Welt der Bashscripte beigetragen hätte wird man kaum sagen können, aber ich habe mir endlich diesen Arrayzugriff so notiert, dass ich ihn das nächste Mal auch finde. ☺

Ist das von Dir oder selbst irgendwo gefunden? Mit dem while drumrum richtiggehend praktisch!

$ uname -a | slice 0 5
Linux ikarus 5.11.2-1-ARCH #1 SMP
$ uname -a | supercut 6 6
Sun Feb 28 22:03:25 UTC 2021 

Aber eine Frage noch: Wer oder was ist supercut? Der 1. Name von slice? ☺

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 10978

user_unknown schrieb:

seahawk1986 schrieb:

Array-Slicing wäre auch eine Möglichkeit (gemäß https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion):

1
2
3
4
5
6
slice () {
  while read -ra line
  do
     echo "${line[@]:$1:$2}" # $1 = index, $2 = length of the slice
  done
}

Siehste! Hat sich doch gelohnt das Posting. Dass ich was sinnvolles zur Welt der Bashscripte beigetragen hätte wird man kaum sagen können, aber ich habe mir endlich diesen Arrayzugriff so notiert, dass ich ihn das nächste Mal auch finde. ☺

Ist das von Dir oder selbst irgendwo gefunden? Mit dem while drumrum richtiggehend praktisch!

Ich habe sicher schon öfter eine Parameter Expansion im Code von anderen gesehen (wenn man nach Array Slicing für die Bash sucht, findet man Beispiele wie z.B. https://stackoverflow.com/a/1336245 - aus dem Bash Manual ist das zugegebenermaßen etwas schwer herauszulesen, wenn man nicht weißt, wonach man suchen muss) und read ist in https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html dokumentiert - grundsätzlich is der Algorithmus nichts besonderes und sieht das in anderen Sprachen nicht viel anders aus:

1
2
3
4
5
6
7
8
#!/usr/bin/env python3
import sys

idx = int(sys.argv[1])
length = int(sys.argv[2])

for fields in (l.split() for l in sys.stdin):
    print(*fields[idx:idx + length])

Oder in C:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {
        const char delimiter[] = " \t";

        if (argc != 3) {
                fprintf(stderr, "usage: %s INDEX LENGTH\n", argv[0]);
                return EXIT_FAILURE;
        }
        const size_t idx = atoi(argv[1]), lenght = atoi(argv[2]), offset = idx + lenght;
        char *line, *r, *token;
        size_t len = 0;


        while (getline(&line, &len, stdin) != -1) {
                size_t n = 0;
                r = line; // we need to keep the original pointer unchanged to free it later
                while ((n <= offset) && (token = strsep(&r, delimiter))) {
                        if (n > idx) printf("%s%s", token, (n != (offset) ? " " : "\n"));
                        ++n;
                }
                free(line);
        }
        return EXIT_SUCCESS;
}

Aber eine Frage noch: Wer oder was ist supercut? Der 1. Name von slice? ☺

Ja, ursprünglich wollte ich einen Wrapper für cut, der den IFS berücksichtigt (weil es nervt immer den Delimiter zu setzen), den ich dann umgebaut habe, weil das etwas wild aussah - und nachdem ich die Daten dann schon mal am IFS aufgetrennt in einem Array hatte, war es einfacher das zu zerlegen als einen String an cut zu verfüttern.

Antworten |