Filtruj listę ciągów z dowolnym warunkiem


4

Czy istnieje sposób na pozostawienie tylko tych ciągów, które spełniają niektóre (potencjalnie dowolne) warunki? Filtrowanie ciągów na podstawie tego, że same pasują do jakiegoś wzorca (z grep) jest banalne. Ale co, jeśli mam listę nazw plików i chcę zostawić tylko te, które są katalogami? Co się stanie, jeśli mam listę adresów URL i chcę zostawić tylko te, które nie zwracają 404, gdy je zapamiętuję? I tak dalej. Czy tego rodzaju logikę można uogólnić za pomocą bash?

Przykład:

$ echo $LIST
/home/me/a
/home/me/b
/home/me/b/some.jpg
$ echo $LIST | ${//%(!$SOME_FANCY_BASH_FILTERING_LOGIC_TO_CHECK_IF_THIS_IS_A_DIRECTORY&%^#}
/home/me/a
/home/me/b

Odpowiedzi:


1

Mówisz o liście potencjalnie czegokolwiek. Jeśli masz listę nazw plików, możesz łatwo utworzyć iterację za pomocą bash i wybrać te, które są katalogami. Jeśli masz listę adresów URL, możesz zrobić to samo, aby sprawdzić, które istnieją w sieci. Ale oczywiście jedyną częścią, którą możesz uogólnić, jest iteracja:

#!/bin/bash
IFS='
'
LIST='1
2
3
'
for I in $LIST
do
  if [ -d $I ]; then
    echo $I is a directory
  elif [ -f $I ]; then
    echo $I is a file
  fi
done

Jeśli masz dwa pliki o nazwach 1 i 3 oraz katalog o nazwie 2, wynikiem będzie:

1 is a file
2 is a directory
3 is a file

Ale jeśli masz listę adresów URL, będziesz musiał zmienić warunki testowania w pętli.


3

Czy tego rodzaju logikę można uogólnić za pomocą bash?

Nie.

Aby jednak filtrować elementy list, pozostawiając tylko te, które są katalogami:

find  $list -maxdepth 0 -type d

Lub,

for d in  $list; do [ -d "$d" ] && echo "$d"; done

Pamiętaj, że przechowywanie plików w zmiennej powłoki, takiej jak list, w przeciwieństwie do tablicy, spowoduje problemy, jeśli którakolwiek z nazw plików zawiera znak spacji.

Podobnie, aby przefiltrować listę serwerów, według których działają (odpowiada na ping):

$ server="yahoo.com google.com nonexistent.com"
$ for s in $server; do ping -qc1 "$s" >/dev/null 2>&1 && echo "$s" ; done
yahoo.com
google.com

0

Możesz zaimplementować własny filtr:

# Filter a list of anything, based on predicate.
# $1: predicate, which take one argument (one item)
# $2: a collections of strings (IFS separated).
filter() {
    items=()
    for x in $2; do
        if $1 "$x"; then
            items+=("$x")
        fi
    done
    echo ${items[@]}
}

A następnie użyj go w następujący sposób:

x() { [ $1 -gt 3 ]; }  # you can make any predicate
filter x "1 2 3 4 5"
-> 4 5

Alternatywnie możemy zdefiniować to jako:

filter() {
    while read line; do
        for x in $line; do
        if $1 "$x"; then
            echo "$x"
        fi
        done
    done
}

Co pozwala nam używać go z rurami:

echo 1 2 3 4 5 | filter x
-> 4
   5

Na koniec obie wersje można połączyć w jedną taką:

filter() {
    inner() (
        for x in $2; do
            $1 "$x" && echo "$x"
        done
    )
    if [ -t 0 ] && [ $# -gt 1 ]; then
        inner $1 "$2"
    else
        while read line; do
            inner $1 "$line"
        done
    fi
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.