RegEx untuk mengekstrak semua kecocokan dari string menggunakan RegExp.exec

175

Saya mencoba menguraikan jenis string berikut:

[key:"val" key2:"val2"]

di mana ada kunci acak: pasangan "val" di dalamnya. Saya ingin mengambil nama kunci dan nilainya. Bagi mereka yang penasaran, saya mencoba mem-parsing format database task warrior.

Inilah string pengujian saya:

[description:"aoeu" uuid:"123sth"]

yang dimaksudkan untuk menyoroti bahwa apa pun bisa menjadi kunci atau nilai selain dari ruang, tidak ada ruang di sekitar titik dua, dan nilai selalu dalam tanda kutip ganda.

Dalam simpul, ini adalah output saya:

[deuteronomy][gatlin][~]$ node
> var re = /^\[(?:(.+?):"(.+?)"\s*)+\]$/g
> re.exec('[description:"aoeu" uuid:"123sth"]');
[ '[description:"aoeu" uuid:"123sth"]',
  'uuid',
  '123sth',
  index: 0,
  input: '[description:"aoeu" uuid:"123sth"]' ]

Tetapi description:"aoeu"juga cocok dengan pola ini. Bagaimana saya bisa mendapatkan semua pertandingan kembali?

Gatlin
sumber
Mungkin saja regex saya salah dan / atau saya hanya menggunakan fasilitas regex di JavaScript secara salah. Ini sepertinya berhasil:> var s = "Lima belas adalah 15 dan delapan adalah 8"; > var re = / \ d + / g; > var m = s.match (re); m = ['15', '8']
gatlin
6
Javascript sekarang memiliki fungsi .match (): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Digunakan seperti ini:"some string".match(/regex/g)
Stefnotch

Jawaban:

237

Lanjutkan menelepon re.exec(s)dalam satu lingkaran untuk mendapatkan semua kecocokan:

var re = /\s*([^[:]+):\"([^"]+)"/g;
var s = '[description:"aoeu" uuid:"123sth"]';
var m;

do {
    m = re.exec(s);
    if (m) {
        console.log(m[1], m[2]);
    }
} while (m);

Cobalah dengan JSFiddle ini: https://jsfiddle.net/7yS2V/

lawnsea
sumber
8
Mengapa tidak whilebukan do … while?
Gumbo
15
Menggunakan loop sementara membuatnya sedikit canggung untuk menginisialisasi m. Anda harus menulis while(m = re.exec(s)), yang merupakan IMO anti-pola, atau Anda harus menulis m = re.exec(s); while (m) { ... m = re.exec(s); }. Saya lebih suka do ... if ... whileidiom, tetapi teknik lain juga akan berhasil.
lawnsea
14
melakukan ini dalam kromium mengakibatkan tab saya mogok.
EdgeCaseBerg
47
@EdgeCaseBerg Anda harus gmengatur flag, jika tidak pointer internal tidak akan bergerak maju Documents .
Tim
12
Poin lain adalah bahwa jika regex dapat cocok dengan string kosong itu akan menjadi loop tak terbatas
FabioCosta
139

str.match(pattern), jika patternmemiliki bendera global g, akan mengembalikan semua kecocokan sebagai array.

Sebagai contoh:

const str = 'All of us except @Emran, @Raju and @Noman was there';
console.log(
  str.match(/@\w*/g)
);
// Will log ["@Emran", "@Raju", "@Noman"]

Anis
sumber
15
Hati-hati: korek api bukan objek yang cocok, tetapi string yang cocok. Misalnya, tidak ada akses ke grup di "All of us except @Emran:emran26, @Raju:raju13 and @Noman:noman42".match(/@(\w+):(\w+)/g)(yang akan kembali ["@Emran:emran26", "@Raju:raju13", "@Noman:noman42"])
madprog
4
@madprog, Benar, ini cara termudah tetapi tidak cocok ketika nilai-nilai grup sangat penting.
Anis
1
Ini tidak bekerja untuk saya. Saya hanya mendapatkan pertandingan pertama.
Anthony Roberts
7
@AnthonyRoberts Anda harus menambahkan bendera "g". /@\w/gataunew RegExp("@\\w", "g")
Aruna Herath
88

Untuk mengulang semua pertandingan, Anda dapat menggunakan replacefungsi:

var re = /\s*([^[:]+):\"([^"]+)"/g;
var s = '[description:"aoeu" uuid:"123sth"]';

s.replace(re, function(match, g1, g2) { console.log(g1, g2); });
Christophe
sumber
Saya pikir itu terlalu rumit. Namun, senang mengetahui tentang berbagai cara melakukan hal yang sederhana (saya memilih jawaban Anda).
Arashsoft
24
Itu kode yang berlawanan dengan intuisi. Anda tidak "mengganti" apa pun dalam arti yang berarti. Hanya mengeksploitasi beberapa fungsi untuk tujuan yang berbeda.
Luke Maurer
6
@dewaskan jika insinyur hanya mengikuti aturan tanpa berpikir di luar kotak, kita bahkan tidak akan berpikir tentang mengunjungi planet lain sekarang ;-)
Christophe
1
@ Maaf, maaf, saya gagal melihat bagian malas di sini. Jika metode yang sama persis disebut "proses" alih-alih "ganti" Anda akan setuju dengan itu. Saya khawatir Anda hanya terjebak pada terminologi.
Christophe
1
@ Christophe Saya jelas tidak terjebak pada terminologi. Saya terjebak pada kode bersih. Menggunakan hal-hal yang dimaksudkan untuk satu tujuan untuk tujuan berbeda disebut "hacky" karena suatu alasan. Ini menciptakan kode membingungkan yang sulit untuk dipahami dan lebih sering tidak memiliki kinerja yang bijaksana. Fakta bahwa Anda menjawab pertanyaan ini tanpa regex dengan sendirinya membuatnya menjadi jawaban yang tidak valid, karena OP menanyakan bagaimana cara melakukannya dengan regex. Namun saya merasa penting untuk menjaga komunitas ini pada standar yang tinggi, itulah sebabnya saya mendukung apa yang saya katakan di atas.
dudewad
56

Ini solusinya

var s = '[description:"aoeu" uuid:"123sth"]';

var re = /\s*([^[:]+):\"([^"]+)"/g;
var m;
while (m = re.exec(s)) {
  console.log(m[1], m[2]);
}

Ini didasarkan pada jawaban lawnsea, tetapi lebih pendek.

Perhatikan bahwa flag `g 'harus diatur untuk menggerakkan pointer internal ke depan di seluruh permintaan.

lovasoa
sumber
17
str.match(/regex/g)

mengembalikan semua kecocokan sebagai sebuah array.

Jika, karena alasan misterius, Anda memerlukan informasi tambahan yang disertakan exec, sebagai alternatif dari jawaban sebelumnya, Anda dapat melakukannya dengan fungsi rekursif alih-alih loop sebagai berikut (yang juga terlihat lebih keren).

function findMatches(regex, str, matches = []) {
   const res = regex.exec(str)
   res && matches.push(res) && findMatches(regex, str, matches)
   return matches
}

// Usage
const matches = findMatches(/regex/g, str)

seperti yang dinyatakan dalam komentar sebelumnya, penting untuk memiliki gpada akhir definisi regex untuk menggerakkan pointer ke depan dalam setiap eksekusi.

noego
sumber
1
Iya. rekursif terlihat elegan dan keren. Loop berulang lurus ke depan, lebih mudah untuk mempertahankan dan men-debug.
Andy N
11

Kami akhirnya mulai melihat matchAllfungsi bawaan, lihat di sini untuk tabel deskripsi dan kompatibilitas . Sepertinya pada Mei 2020, Chrome, Edge, Firefox, dan Node.js (12+) didukung tetapi tidak untuk IE, Safari, dan Opera. Sepertinya itu dirancang pada Desember 2018 jadi beri waktu untuk menjangkau semua browser, tapi saya percaya itu akan sampai di sana.

Built-in matchAllfungsi baik karena mengembalikan sebuah iterable . Ini juga mengembalikan grup penangkap untuk setiap pertandingan! Jadi Anda bisa melakukan hal-hal seperti

// get the letters before and after "o"
let matches = "stackoverflow".matchAll(/(\w)o(\w)/g);

for (match of matches) {
    console.log("letter before:" + match[1]);
    console.log("letter after:" + match[2]);
}

arrayOfAllMatches = [...matches]; // you can also turn the iterable into an array

Sepertinya setiap objek pertandingan menggunakan format yang sama dengan match(). Jadi setiap objek adalah array dari pertandingan dan menangkap kelompok, bersama dengan tiga sifat tambahan index, inputdan groups. Jadi sepertinya:

[<match>, <group1>, <group2>, ..., index: <match offset>, input: <original string>, groups: <named capture groups>]

Untuk informasi lebih lanjut tentang matchAllada juga halaman pengembang Google . Ada juga polyfill / shims yang tersedia.

woojoo666
sumber
Saya sangat suka ini, tetapi belum cukup mendarat di Firefox 66.0.3. Caniuse juga belum memiliki daftar dukungan tentang hal itu. Saya menantikan yang ini. Saya melihatnya bekerja di Chromium 74.0.3729.108.
Lonnie Best
1
@LonnieBest ya Anda dapat melihat bagian kompatibilitas halaman MDN yang saya tautkan . Sepertinya Firefox mulai mendukungnya dalam versi 67. Tetap tidak akan merekomendasikan menggunakannya jika Anda mencoba mengirimkan produk. Ada polyfill / shims yang tersedia, yang saya tambahkan ke jawaban saya
woojoo666
10

Berdasarkan fungsi Agus, tapi saya lebih suka mengembalikan nilai kecocokan:

var bob = "&gt; bob &lt;";
function matchAll(str, regex) {
    var res = [];
    var m;
    if (regex.global) {
        while (m = regex.exec(str)) {
            res.push(m[1]);
        }
    } else {
        if (m = regex.exec(str)) {
            res.push(m[1]);
        }
    }
    return res;
}
var Amatch = matchAll(bob, /(&.*?;)/g);
console.log(Amatch);  // yeilds: [&gt;, &lt;]
bob
sumber
8

Iterables lebih baik:

const matches = (text, pattern) => ({
  [Symbol.iterator]: function * () {
    const clone = new RegExp(pattern.source, pattern.flags);
    let match = null;
    do {
      match = clone.exec(text);
      if (match) {
        yield match;
      }
    } while (match);
  }
});

Penggunaan dalam satu lingkaran:

for (const match of matches('abcdefabcdef', /ab/g)) {
  console.log(match);
}

Atau jika Anda menginginkan array:

[ ...matches('abcdefabcdef', /ab/g) ]
sdgfsdh
sumber
1
Typo: if (m)should beif (match)
Botje
Array sudah dapat diubah, sehingga semua orang yang mengembalikan array pertandingan juga mengembalikan iterables. Apa yang lebih baik adalah jika Anda konsol log array, browser dapat benar-benar mencetak konten. Tapi konsol mencatat versi generik hanya membuat Anda [Objek objek] {...}
StJohn3D
Semua array bisa diubah tetapi tidak semua itu adalah array. Sebuah iterable lebih unggul jika Anda tidak tahu apa yang perlu dilakukan penelepon. Misalnya, jika Anda hanya ingin pertandingan pertama, iterable lebih efisien.
sdgfsdh
impian Anda menjadi kenyataan, browser meluncurkan dukungan untuk built-in matchAllyang mengembalikan iterable : D
woojoo666
1
Saya telah menemukan jawaban ini implementasi post-matchAll. Saya menulis beberapa kode untuk browser JS yang mendukungnya, tetapi Node sebenarnya tidak. Ini berperilaku identik dengan matchAll jadi saya tidak perlu menulis ulang hal-hal - Ceria!
user37309
8

Jika Anda memiliki ES9

(Artinya jika sistem Anda: Chrome, Node.js, Firefox, dll mendukung Ecmascript 2019 atau lebih baru)

Gunakan yang baru yourString.matchAll( /your-regex/ ).

Jika Anda tidak memiliki ES9

Jika Anda memiliki sistem yang lebih lama, inilah fungsi untuk menyalin dan menempel dengan mudah

function findAll(regexPattern, sourceString) {
    let output = []
    let match
    // make sure the pattern has the global flag
    let regexPatternWithGlobal = RegExp(regexPattern,"g")
    while (match = regexPatternWithGlobal.exec(sourceString)) {
        // get rid of the string copy
        delete match.input
        // store the match data
        output.push(match)
    } 
    return output
}

contoh penggunaan:

console.log(   findAll(/blah/g,'blah1 blah2')   ) 

output:

[ [ 'blah', index: 0 ], [ 'blah', index: 6 ] ]
Jeff Hykin
sumber
5

Inilah fungsi saya untuk mendapatkan kecocokan:

function getAllMatches(regex, text) {
    if (regex.constructor !== RegExp) {
        throw new Error('not RegExp');
    }

    var res = [];
    var match = null;

    if (regex.global) {
        while (match = regex.exec(text)) {
            res.push(match);
        }
    }
    else {
        if (match = regex.exec(text)) {
            res.push(match);
        }
    }

    return res;
}

// Example:

var regex = /abc|def|ghi/g;
var res = getAllMatches(regex, 'abcdefghi');

res.forEach(function (item) {
    console.log(item[0]);
});
Agus Syahputra
sumber
Solusi ini mencegah loop tak terbatas ketika Anda lupa menambahkan bendera global.
user68311
2

Sejak ES9, sekarang ada cara yang lebih sederhana dan lebih baik untuk mendapatkan semua pertandingan, bersama dengan informasi tentang kelompok tangkap, dan indeks mereka:

const string = 'Mice like to dice rice';
const regex = /.ice/gu;
for(const match of string.matchAll(regex)) {
    console.log(match);
}

// ["tikus", indeks: 0, masukan: "tikus suka memotong dadu", grup: tidak ditentukan]

// ["dadu", indeks: 13, masukan: "tikus suka memotong dadu", grup: tidak terdefinisi]

// ["nasi", indeks: 18, masukan: "tikus suka memotong dadu", grup: tidak terdefinisi]

Saat ini didukung di Chrome, Firefox, Opera. Bergantung pada saat Anda membaca ini, periksa tautan ini untuk melihat dukungannya saat ini.

iuliu.net
sumber
Hebat! Tetapi tetap penting untuk diingat bahwa regex harus memiliki flag gdan itu lastIndexharus diatur ulang ke 0 sebelum doa matchAll.
N. Kudryavtsev
1

Gunakan ini...

var all_matches = your_string.match(re);
console.log(all_matches)

Ini akan mengembalikan array semua pertandingan ... Itu akan bekerja dengan baik .... Tapi ingat itu tidak akan mengambil kelompok dalam akun .. Itu hanya akan mengembalikan pertandingan penuh ...

Subham Debnath
sumber
0

Saya pasti akan merekomendasikan menggunakan fungsi String.match (), dan membuat RegEx yang relevan untuk itu. Contoh saya adalah dengan daftar string, yang sering diperlukan saat memindai input pengguna untuk kata kunci dan frasa.

    // 1) Define keywords
    var keywords = ['apple', 'orange', 'banana'];

    // 2) Create regex, pass "i" for case-insensitive and "g" for global search
    regex = new RegExp("(" + keywords.join('|') + ")", "ig");
    => /(apple|orange|banana)/gi

    // 3) Match it against any string to get all matches 
    "Test string for ORANGE's or apples were mentioned".match(regex);
    => ["ORANGE", "apple"]

Semoga ini membantu!

Sebastian Scholl
sumber
0

Ini sebenarnya tidak akan membantu masalah Anda yang lebih rumit, tetapi saya tetap mempostingnya karena ini adalah solusi sederhana untuk orang-orang yang tidak melakukan pencarian global seperti Anda.

Saya telah menyederhanakan regex dalam jawaban agar lebih jelas (ini bukan solusi untuk masalah Anda yang sebenarnya).

var re = /^(.+?):"(.+)"$/
var regExResult = re.exec('description:"aoeu"');
var purifiedResult = purify_regex(regExResult);

// We only want the group matches in the array
function purify_regex(reResult){

  // Removes the Regex specific values and clones the array to prevent mutation
  let purifiedArray = [...reResult];

  // Removes the full match value at position 0
  purifiedArray.shift();

  // Returns a pure array without mutating the original regex result
  return purifiedArray;
}

// purifiedResult= ["description", "aoeu"]

Yang terlihat lebih bertele-tele daripada karena komentar, ini seperti apa tanpa komentar

var re = /^(.+?):"(.+)"$/
var regExResult = re.exec('description:"aoeu"');
var purifiedResult = purify_regex(regExResult);

function purify_regex(reResult){
  let purifiedArray = [...reResult];
  purifiedArray.shift();
  return purifiedArray;
}

Perhatikan bahwa grup apa pun yang tidak cocok akan terdaftar dalam array sebagai undefinednilai.

Solusi ini menggunakan operator penyebaran ES6 untuk memurnikan array nilai spesifik regex. Anda perlu menjalankan kode Anda melalui Babel jika Anda menginginkan dukungan IE11.

Daniel Tonon
sumber
0

Inilah solusi satu baris tanpa loop sementara .

Urutan disimpan dalam daftar yang dihasilkan.

Kerugian potensial adalah

  1. Itu klon regex untuk setiap pertandingan.
  2. Hasilnya dalam bentuk yang berbeda dari solusi yang diharapkan. Anda harus memprosesnya sekali lagi.
let re = /\s*([^[:]+):\"([^"]+)"/g
let str = '[description:"aoeu" uuid:"123sth"]'

(str.match(re) || []).map(e => RegExp(re.source, re.flags).exec(e))

[ [ 'description:"aoeu"',
    'description',
    'aoeu',
    index: 0,
    input: 'description:"aoeu"',
    groups: undefined ],
  [ ' uuid:"123sth"',
    'uuid',
    '123sth',
    index: 0,
    input: ' uuid:"123sth"',
    groups: undefined ] ]
Jae Won Jang
sumber
0

Dugaan saya adalah bahwa jika akan ada kasus tepi seperti ruang ekstra atau hilang, ungkapan ini dengan batas kurang mungkin juga menjadi pilihan:

^\s*\[\s*([^\s\r\n:]+)\s*:\s*"([^"]*)"\s*([^\s\r\n:]+)\s*:\s*"([^"]*)"\s*\]\s*$

Jika Anda ingin menjelajahi / menyederhanakan / memodifikasi ekspresi, sudah dijelaskan di panel kanan atas regex101.com . Jika mau, Anda juga dapat menonton di tautan ini , bagaimana itu cocok dengan beberapa input sampel.


Uji

const regex = /^\s*\[\s*([^\s\r\n:]+)\s*:\s*"([^"]*)"\s*([^\s\r\n:]+)\s*:\s*"([^"]*)"\s*\]\s*$/gm;
const str = `[description:"aoeu" uuid:"123sth"]
[description : "aoeu" uuid: "123sth"]
[ description : "aoeu" uuid: "123sth" ]
 [ description : "aoeu"   uuid : "123sth" ]
 [ description : "aoeu"uuid  : "123sth" ] `;
let m;

while ((m = regex.exec(str)) !== null) {
    // This is necessary to avoid infinite loops with zero-width matches
    if (m.index === regex.lastIndex) {
        regex.lastIndex++;
    }
    
    // The result can be accessed through the `m`-variable.
    m.forEach((match, groupIndex) => {
        console.log(`Found match, group ${groupIndex}: ${match}`);
    });
}

Sirkuit RegEx

jex.im memvisualisasikan ekspresi reguler:

masukkan deskripsi gambar di sini

Emma
sumber
-5

Inilah jawaban saya:

var str = '[me nombre es] : My name is. [Yo puedo] is the right word'; 

var reg = /\[(.*?)\]/g;

var a = str.match(reg);

a = a.toString().replace(/[\[\]]/g, "").split(','));
daguang
sumber
3
String input Anda ( str) memiliki format yang salah (terlalu banyak tanda kurung keras). Anda hanya menangkap kunci, bukan nilainya. Kode Anda memiliki kesalahan sintaks dan dan tidak mengeksekusi (kurung terakhir). Jika Anda menjawab pertanyaan "lama" dengan jawaban yang sudah diterima, pastikan Anda menambahkan lebih banyak pengetahuan dan jawaban yang lebih baik daripada yang sudah diterima. Saya tidak berpikir jawaban Anda melakukan itu.
Dihapus