Příkaz FINDSTR se používá k hledání textového řetězce v jednom nebo více souborech pomocí regulárních výrazů. Ve srovnání s příkazem FIND vám tento příkaz umožňuje provádět mnohem flexibilnější vyhledávání v souladu s pravidly zadanými jako parametry příkazového řádku. Regulární výrazy jsou druhem jazyka používajícího regulární a speciální znaky, které definují vzor a vyhledávací algoritmus. Obyčejné znaky (literály) jsou běžné textové znaky – písmena, čísla, interpunkční znaménka atd. Speciální znaky (metasymboly) jsou prvky pravidel záznamu a parametrů zpracování pro běžné znaky. Tedy například symbol tečky. znamená „libovolný znak“, hranaté závorky představují sadu znaků v nich uzavřených, sekvence d je libovolný číselný znak, D je jakýkoli nečíselný znak.
Když je nutné zacházet s metaznaky jako s běžnými textovými prvky, používají regulární výrazy znak escape – zpětné lomítko. Zápis [ znamená běžný znak v hranaté závorce, nikoli metaznak pro začátek psaní. Chcete-li uniknout více metaznakům, použijte sekvenci:
Q. . . sada metaznaků. . .E
Zpětné lomítko před běžným znakem znamená, že je interpretováno jako speciální znak:
s – odpovídá znaku mezery.
Při použití v regulárních výrazech se rozlišují malá a velká písmena.
S — libovolný znak, nikoli mezera.
Formát příkazového řádku FINDSTR:
FINDSTR [/B] [/E] [/L] [/R] [/S] [/I] [/X] [/V] [/N] [/M] [/O] [/P] [ /F:soubor] [/C:řádek] [/G:soubor] [/D:seznam_složek] [/A:barvy] [/OFF[LINE]] řádky [[jednotka:][cesta]název souboru[ . ]]
/B – Hledat vzor pouze na začátku řádků.
/E – Vzor hledat pouze na konci řádků.
/L – Hledat řetězce doslovně.
/R – Hledání řetězců jako regulárních výrazů.
/S – Vyhledá soubory v aktuální složce a všech jejích podsložkách.
/I – Určuje, že vyhledávání nebude rozlišovat malá a velká písmena.
/X – Vytiskne řádky, které se přesně shodují.
/V – Vytiskne řádky, které neobsahují shody s hledanými.
/N – Vytiskne číslo řádku, kde byla nalezena shoda, a její obsah.
/M – Vytiskne pouze název souboru, ve kterém byla nalezena shoda.
/O – Vytiskne nalezený řádek oddělený prázdným řádkem.
/P – Přeskočí řádky obsahující netisknutelné znaky.
/OFF[LINE] – Nepřeskakuje soubory s nastaveným atributem „Offline“.
/A:colors – Dvě hexadecimální číslice – atributy barev. Viz “BARVA /?”
/F:soubor – Načte seznam souborů z daného souboru (/ pro konzolu).
/C:string – používá zadaný řetězec jako vyhledávací frázi.
/G:file – Získá řádky z daného souboru (/ pro konzolu).
/D:seznam_složek – Hledání v seznamu složek (oddělených středníkem).
řetězec – Vyhledat text.
[jednotka:][cesta]název souboru – Určuje název souboru nebo souborů.
Mezera se používá k oddělení více vyhledávacích řetězců, pokud argument nemá předponu /C. Například,
FINDSTR “Ahoj světe” file.txt vyhledejte “Hello” nebo “world” v souboru.txt
FINDSTR /C: “Hello world” file.txt hledá řetězec “Hello world” v souboru file.txt.
Pro rychlou nápovědu k použití příkazu FINDSTR použijte /? :
Kromě možností příkazového řádku je nápověda doplněna stručným shrnutím syntaxe regulárních výrazů:
. – Jakýkoli symbol.
* – Opakovat: žádný nebo více výskytů předchozího znaku nebo třídy
^ — Pozice v řádku: začátek řádku
$ — Pozice v řádku: konec řádku
[class] – Třída znaků: libovolný jednotlivý znak ze sady
[^class] – Reverzní třída znaků: libovolný jednotlivý znak z doplňku
[xy] — Rozsah: libovolné znaky ze zadaného rozsahu
x — Symbol služby: symbolické označení symbolu služby x
— Pozice ve slově: na začátku slova
xyz > – Pozice ve wordu: na konci slova
Úplné informace o regulárních výrazech FINDSTR naleznete v dostupné online dokumentaci.
Příklady použití FINDSTR:
findstr /M [0-9] %temp%*.* – zobrazí seznam souborů (klávesa /M) obsahující čísla (nastavena 0-9) z adresáře dočasných souborů (definovaného pomocí %TEMP%)
findstr /P /I “Chyba” %temp%*.* – Zobrazí řádky obsahující slovo Error . Vyhledávání řetězců bez zohlednění velikosti písmen (klávesa /I), řádky obsahující netisknutelné znaky se nezobrazují (klávesa /P).
findstr /M /I /C:”chyba sítě” %windir%system32*.exe – zobrazí seznam spustitelných souborů ze systémového adresáře Windowssystem32, které obsahují řetězec “chyba sítě”
findstr /s /I /A:f4 /O /C:»failed» C:*.log – zobrazení řádků souborů s příponou log obsahující slovo selhalo . Název souboru a odsazení řádku vzhledem k jeho počátku jsou zobrazeny červenými znaky na bílém pozadí (klávesa /A:F4). Vyhledávání se provádí ve všech souborech .log kořenového adresáře disku C: a všech jeho podadresářích (přepínač /S)
findstr /A:FC /N /s /i” – zobrazí řádky obsahující slovo začínající na “comput” (počítač, počítač, počítače atd.), stejně jako názvy souborů a čísla řádků (klíč /N).
findstr /A:FC /N /s /i ” – jako v předchozím případě, ale hledá se řetězec obsahující slovo začínající podřetězcem. Při používání znaků ruského jazyka je třeba vzít v úvahu jejich kódování, protože kódy znaků v kódování DOS a Windows se liší. V dávkových souborech, když je nutné hledat řetězce obsahující znaky národní abecedy, musí být vyhledávací vzor reprezentován ve stejném kódování jako obsah souboru. Před vyhledáváním můžete použít přepínání kódové stránky:
REM přepnout na kódování Windows
chcp 1251
REM Provádění vyhledávání
findstr /A:FC /N /s /i “
Zvažte problém, který nastane pokaždé, když stisknete ctrl+f:
Je tam velký text (t). Musíme v něm najít všechny výskyty řetězců (řetězců).
Naivní řešení s porovnáváním všech podřetězců (t) délky (|s|) s řetězci (s) běží v (O(|t| cdot |s|)) . Pokud je text velký, hledání dlouhých slov v něm zabere hodně času.
Existuje však mnoho způsobů, jak tento problém vyřešit v (O(|s| + |t|)) , dva z nejběžnějších a nejjednodušších jsou: funkce předpony a skrz z-funkce (poznámka: ne „zi“, ale „zet“).
Funkce předpony
Definice. Předponová funkce z řetězce (s) je pole (p), kde (p_i) se rovná délce největší předpony řetězce (s_0 s_1 s_2 ldots s_i), což je také přípona (i) předpona (nepočítá se celá (i) -tá předpona).
Například největší předpona, která se rovná příponě pro řetězec “aataataa” je “aataa”; funkce předpony pro tento řetězec je ([0, 1, 0, 1, 2, 3, 4, 5]) .
vectorint> slow_prefix_function(string s) int n = (int) s.size(); vectorint> p(n, 0); for (int i = 1; i n; i++) for (int len = 1; len i; len++) // если префикс длины len равен суффиксу длины len if (s.substr(0, len) == s.substr(i - len + 1, len)) p[i] = len; return p; > Tento algoritmus aktuálně běží v (O(n^3)), ale později jej urychlíme.
Jak to pomůže vyřešit původní problém?
Věřme zatím, že funkci předpony můžeme považovat za lineární ve velikosti řetězce a naučíme se ji používat k hledání podřetězce v řetězci.
Spojme podřetězce (s) a (t) nějakým symbolem, který se nevyskytuje ani tam, ani tam – označme tento symbol #. Podívejme se na funkci předpony výsledného řetězce (s#t) .
string s = "choose"; string t = "choose life. choose a job. choose a career. choose a family. choose a fu. "; cout s + "#" + t endl; cout slow_prefix_function(s + "#" + t) endl; choose#choose life. choose a job. choose a career. choose a family. choose a fu. 0000000123456000000012345600000000123456000100000001234560000000000012345600000000 Můžete vidět, že všechna místa, kde se hodnoty rovnají 6 (délka (s)), jsou konce výskytů (s) v textu (t).
Takový algoritmus (vypočítejte prefixovou funkci z (s#t) a podívejte se, na jakých pozicích se rovná (|s|)) se nazývá Knuth-Morris-Prattův algoritmus.
Jak to rychle spočítat
Podívejme se na několik dalších příkladů funkcí prefixů a pokusíme se najít vzory:
aaaaa 01234 abcdef 000000 abacabadava 00101230101 Můžete si všimnout následující vlastnosti: (p_) je nejvýše o jednu větší než (p_i) .
Důkaz. Pokud existuje předpona rovna příponě řetězce (s_) , length (p_) , pak zahozením posledního znaku můžete získat správnou příponu pro řetězec (s_), jehož délka bude přesně o jednu kratší. .
Pokusme se problém vyřešit pomocí dynamiky: najděte vzorec pro (p_i) přes předchozí hodnoty.
Všimněte si, že (p_ = p_i + 1) právě tehdy, když (s_ =s_) . V tomto případě můžeme jednoduše aktualizovat (p_) a jít dál.
Například v řádku (underbracetoverbrace) je zvýrazněna maximální předpona, která se rovná příponě: (p_ = 5) . Pokud je další znak roven (t), pak (p_ = p_ + 1 = 6) .
Ale co se stane, když (s_neq s_) ? Nechť další znak ve stejném příkladu není roven (t), ale (b).
- (implicitně) Délka předpony rovnající se příponě nového řádku bude přesně menší než 5.
- (implikuje) Kromě toho, že novou předponu, kterou hledáme, je přípona „aabaab“, je to také předpona podřetězce „aabaa“.
- (implicitně) Dalším kandidátem na ověření je tedy hodnota prefixové funkce z „aabaa“, tedy (p_4 = 2), kterou jsme již vypočítali.
- (implicitně) Jestliže (s_2 = s_) (tj. nový znak odpovídá znaku, který následuje za kandidátskou předponou), pak (p_ = p_2 + 1 = 2 + 1 = 3) .
V tomto případě tomu tak skutečně je (požadovaná předpona je „aab“). Ale co když v obecném případě (p_ neq p_) ? Pak provedeme stejnou úvahu a získáme nového kandidáta kratší délky – (p_>) . Pokud tento nefunguje, kontrolujeme stejným způsobem menší, dokud tento index nebude nulový.
vectorint> prefix_function(string s) int n = (int) s.size(); vectorint> p(n, 0); for (int i = 1; i n; i++) // префикс функция точно не больше этого значения + 1 int cur = p[i - 1]; // уменьшаем cur значение, пока новый символ не сматчится while (s[i] != s[cur] && cur > 0) cur = p[cur - 1]; // здесь либо s[i] == s[cur], либо cur == 0 if (s[i] == s[cur]) p[i] = cur + 1; > return p; > Asymptotika. V nejhorším případě může tento while běžet (O(n)) krát za iteraci, ale v průměru každý while běží v (O(1)) .
Funkce předpony se v každém kroku zvýší maximálně o jednu a po každé iteraci while se sníží alespoň o jednu. To znamená, že celkový počet operací nebude větší než (O(n)).
Z-funkce
Poněkud srozumitelnější alternativou k funkci předpony je funkce z.
Z-funkce řetězce (s) je definována jako pole (z) takové, že (z_i) se rovná délce maximálního podřetězce, spouštění z (i)té pozice, která se rovná předponě (s).
[underbracecoverbracedaba hspace (z_4 = 3)]
vectorint> slow_z_function (string s) int n = (int) s.size(); vectorint> z(n, 0); // z[0] считается не определенным for (int i = 1; i n; i++) // если мы не вышли за границу и следующие символы совпадают while (i + z[i] n && s[z[i]] == s[i + z[i]]) z[i]++; return z; > aaaaa 04321 abcdef 000000 abacabadava 00103010101 Z-funkci lze použít místo prefixové funkce v algoritmu Knuth-Morris-Pratt – pouze nyní budou požadované pozice začínat (|s|) spíše než končit. Zbývá se jen naučit, jak to hledat v (O(n)) .
Jak to rychle spočítat
Půjdeme zleva doprava a uložíme z-blok — pravý podřetězec rovný prefixu, který se nám podařilo objevit. Jeho hranice budeme označovat jako (l) a (r) včetně.
Pojďme nyní chtít najít (z_i) , a už jsme našli všechny předchozí. Nový (i)tý znak může ležet buď napravo od z-bloku nebo uvnitř něj:
- Pokud vpravo, pak jednoduše naivně vyhledáme (z_i) (maximální segment začínající (s_i) a rovný prefixu) a prohlásíme jej za nový z-blok.
- Pokud (i)-tý prvek leží uvnitř z-boxu, pak se můžeme podívat na hodnotu (z_) a použít ji k inicializaci (z_i) na něco, možná nenulového. Pokud je (z_) vlevo od pravého okraje (z)-bloku, pak (z_i = z_) – nemůže jich být více (z_i). Pokud narazí na hranici, pak ji k ní „přiřízneme“ a zvýšíme o jednu.
vectorint> z_function (string s) int n = (int) s.size(); vectorint> z(n, 0); int l = 0, r = 0; for (int i = 1; i n; i++) // если мы уже видели этот символ if (i r) // то мы можем попробовать его инициализировать z[i - l], // но не дальше правой границы: там мы уже ничего не знаем z[i] = min(r - i + 1, z[i - l]); // дальше каждое успешное увеличение z[i] сдвинет z-блок на единицу while (i + z[i] n && s[z[i]] == s[i + z[i]]) z[i]++; // проверим, правее ли мы текущего z-блока if (i + z[i] - 1 > r) r = i + z[i] - 1; l = i; > > return z; > Asymptotika. V algoritmu provedeme stejný počet akcí, jako se posune pravá hranice z-bloku – a to je (O(n)).
Porovnání
Obecně jsou funkce z a předpony velmi podobné, ale algoritmus Knuth-Morris-Pratt je ve všech klasických učebnicích programování a z nějakého důvodu o funkci z kromě programátorů olympiád ví jen málo lidí.
O funkci prefixu je také důležité vědět, že je online – stačí přečíst další znak a hned zjistíte význam.
Cvičení 1. Je dána řada prefixových funkcí. Původní řetězec není uveden. Vypočtěte v (O(n)) čase z-funkci tohoto řetězce.
Cvičení 2. Je dáno pole z-funkcí. Původní řetězec není uveden. Vypočtěte v čase (O(n)) funkci předpony tohoto řetězce.