you can use $zstrip, where action *P, * is for any place, and P any punctuation

for example

USER>write $zstrip("before!@#$%^&*()_+\-=\[\]{};':""\\|,.<>\/?after", "*P")
beforeafter

And if you just want to check if the string contains any punctuations, you can compare if the original string does not equal the same after $zstrip.

if you wish to use regexp, you can use $match

USER>write $match(".!@", "^[!@#$%^&*()_+\-=\[\]{};':""\\|,.<>\/?]*$")
1

While my global is quite simple, and it's size is about 1.1 GB, or around 18 bytes per record

USER>do ##class(%GlobalEdit).GetGlobalSize("/usr/irissys/mgr/user","YYY",.all,.used)

USER>zw all
all=1109

USER>w all*1024*1024/65000000
17.89031975384615385

After restarting, with a cold cache, I got 17 seconds

USER>s sub="", count = 0, ts = $zh for { set sub = $Order(^YYY(sub)) quit:sub=""  set count = count + 1 } write !,"elapsed: ", $zh-ts 

elapsed: 16.994676

So, my disk can read around 65MB per sec

If your enterprise still uses mechanical disks, that's still a factor that has to be considered

All the blocks required to be read to get through the list can be placed all around the disk/s. And it takes time. On a live system, with many changes, and when a lot of different data is stored, the next block can be far from the previous one. So, on mechanical discs, defragmentation is matter, and may slow the speed.

I don't know the character of your data, but if you have a lot of data, stored in the globals, it will require to read much more blocks, to even just count the items.

And most probably the easiest way to solve it, is just to use bitmap index.