Jak przekazać symbol wieloznaczny „*” do parametru ścieżki polecenia find za pomocą zmiennej w skrypcie?

9

Chcę używać finddo znajdowania plików w zestawie folderów ograniczonych symbolami wieloznacznymi, ale tam, gdzie w nazwie ścieżki są spacje.

Z linii poleceń jest to łatwe. Wszystkie poniższe przykłady działają.

find  te*/my\ files/more   -print
find  te*/'my files'/more  -print
find  te*/my' 'files/more  -print

Znajdą one na przykład pliki terminal/my files/morei tepid/my files/more.

Potrzebuję tego jednak, aby był częścią skryptu; potrzebuję czegoś takiego:

SEARCH='te*/my\ files/more'
find ${SEARCH} -print

Niestety, cokolwiek robię, nie jestem w stanie mieszać symboli wieloznacznych i spacji w findpoleceniu w skrypcie. Powyższy przykład zwraca następujące błędy (zwróć uwagę na nieoczekiwane podwojenie odwrotnego ukośnika):

find: te*/my\\’: No such file or directory
find: files/more’: No such file or directory

Próba użycia cytatów również się nie udaje.

SEARCH="te*/'my files'/more"
find ${SEARCH} -print

Zwraca następujące błędy, ignorując znaczenie cudzysłowów:

find: te*/'my’: No such file or directory
find: ‘files'/more’: No such file or directory

Oto jeszcze jeden przykład.

SEARCH='te*/my files/more'
find ${SEARCH} -print

Zgodnie z oczekiwaniami:

find: te*/my’: No such file or directory
find: files/more’: No such file or directory

Każda odmiana, którą próbowałem, zwraca błąd.

Mam obejście, które jest potencjalnie niebezpieczne, ponieważ zwraca zbyt wiele folderów. Konwertuję wszystkie spacje na znak zapytania (jednoznakowy symbol wieloznaczny) w następujący sposób:

SEARCH='te*/my files/more'
SEARCH=${SEARCH// /?}       # Convert every space to a question mark.
find ${SEARCH} -print

Jest to odpowiednik:

find te*/my?files/more -print

Zwraca to nie tylko prawidłowe foldery, ale także terse/myxfiles/more, czego nie powinno.

Jak mogę osiągnąć to, co próbuję zrobić? Google mi nie pomogło :(

Paddy Landau
źródło
@KasiyA Używam bash; musisz używać czegoś innego, ponieważ wcześniej nie widziałem tej konstrukcji. Polecenie powoduje SEARCH: command not foundwykonanie polecenia find -print.
Paddy Landau
Strzał w ciemności, ale co z cytowaniem? find "${SEARCH}" -print?
Alaa Ali,
@AlaaAli Nie, to nie działa, ponieważ cytowanie uniemożliwia Bashowi używanie symboli wieloznacznych. Będzie szukał ścieżki konkretnie z nazwą (w moim przykładzie) te*/'my files'/more.
Paddy Landau

Odpowiedzi:

9

Dokładnie to samo polecenie powinno działać poprawnie w skrypcie:

#!/usr/bin/env bash
find  te*/my\ files/ -print

Jeśli potrzebujesz mieć ją jako zmienną, staje się ona nieco bardziej złożona:

#!/usr/bin/env bash
search='te*/my\ files/'
eval find "$search" -print

OSTRZEŻENIE:

Używanie w evalten sposób nie jest bezpieczne i może spowodować wykonanie dowolnego i potencjalnie szkodliwego kodu, jeśli nazwy plików mogą zawierać określone znaki. Szczegółowe informacje można znaleźć w bash FAQ 48 .

Lepiej podać ścieżkę jako argument:

#!/usr/bin/env bash
find "$@" -name "file*"

Innym podejściem jest findcałkowite unikanie i korzystanie z rozszerzonych funkcji globowania i globów bash:

#!/usr/bin/env bash
shopt -s globstar
for file in te*/my\ files/**; do echo "$file"; done

Opcja globstarbash umożliwia **dopasowanie rekurencyjne:

globstar
      If set, the pattern ** used in a pathname expansion con
      text will match all files and zero or  more  directories
      and  subdirectories.  If the pattern is followed by a /,
      only directories and subdirectories match.

Aby działał w 100% jak znajdź i dołącz pliki dot (pliki ukryte), użyj

#!/usr/bin/env bash
shopt -s globstar
shopt -s dotglob
for file in te*/my\ files/**; do echo "$file"; done

Możesz nawet echoje bezpośrednio bez pętli:

echo te*/my\ files/**
terdon
źródło
2
Ustawiłem to jako odpowiedź ze względu na pomocne komentarze, które wypowiedział terdon (nie zapominając o pomocnych komentarzach innych). Użyłem możliwości globowania Basha w wierszu poleceń, aby przekazać wiele ścieżek do mojego skryptu, zamiast skryptu próbującego go rozwiązać. To dobrze działa.
Paddy Landau
2

Co powiesz na tablice?

$ tree Desktop/ Documents/
Desktop/
└── my folder
    └── more
        └── file
Documents/
└── my folder
    ├── folder
    └── more

5 directories, 1 file
$ SEARCH=(D*/my\ folder)
$ find "${SEARCH[@]}" 
Desktop/my folder
Desktop/my folder/more
Desktop/my folder/more/file
Documents/my folder
Documents/my folder/more
Documents/my folder/folder

(*)rozwija się do tablicy wszystkiego, co pasuje do znaku wieloznacznego. I "${SEARCH[@]}"rozwija się do wszystkich elementów w tablicy ( [@]), każdy z osobna cytowany.

Z opóźnieniem zdaję sobie sprawę, że powinna być w stanie to zrobić. Coś jak:

find . -path 'D*/my folder/more/'
muru
źródło
Sprytny pomysł, ale niestety to nie działa. Dlaczego? Ponieważ sama ścieżka jest utrzymywana w zmiennej; stąd INPUTPATH='te*/my files/morei SEARCH=(${INPUTPATH}). Bez względu na to, jak zmieniam sposób, w jaki to robię, nadal mam wynik niefunkcjonalny. To wydaje się niemożliwe!
Paddy Landau
To wszystko oczywiście prawda, ale OP musi to zrobić w skrypcie. To zmienia rzeczy, ponieważ ekspansja symboli wieloznacznych staje się znacznie bardziej złożona i to nie działa.
terdon
@PaddyLandau W takim razie, dlaczego nie można użyć find„s -pathfiltr? Używa symboli wieloznacznych i zdecydowanie nie wymaga rozszerzenia.
mur
@muru To ciekawe; Nie wiedziałam o tym -path. Jednak w ciągu ostatnich 10 minut wymyśliłem odpowiedź: użyj eval! Wydaje się to prostsze niż -path.
Paddy Landau
2
@terdon i muru i wszyscy: dziękuję. Słyszałem, co wszyscy mówiliście, i zdałem sobie sprawę, że powinienem zmusić mój skrypt do zrobienia jednej rzeczy i pozwolić Bashowi na globbing przekazywać wiele plików lub ścieżek do skryptu. W ten sposób zmodyfikowałem skrypt. Działa dobrze i lepiej pasuje do filozofii Linux. Jeszcze raz dziękuję!
Paddy Landau
0

W końcu znalazłem odpowiedź.

Dodaj ukośnik do wszystkich spacji:

SEARCH='te*/my files/more'
SEARCH=${SEARCH// /\\ }

W tym momencie SEARCHzawiera te*/my\ files/more.

Następnie użyj eval.

eval find ${SEARCH} -print

To takie proste! Użycie evalpomija interpretację ${SEARCH}pochodzącą ze zmiennej.

Paddy Landau
źródło
Proszę nie. .
terdon
@terdon Dziękuję za ostrzeżenie. Wróć do deski kreślarskiej!
Paddy Landau
Tak, to zaskakująco trudne. Właśnie zaktualizowałem swoją odpowiedź innym podejściem, dlaczego zamiast tego nie użyć globowania? Jeśli to nadal nie działa, sugeruję, abyś opublikował nowe pytanie na Unixie i Linuksie wyjaśniające, jaki jest twój ostateczny cel i dlaczego musisz mieć wzorzec jako zmienną. Tego rodzaju rzeczy są bardziej prawdopodobne, aby uzyskać lepszą odpowiedź.
terdon