
Show HN: Xo, a sed-like command line utility that composes regex matches - ezekg
https://github.com/ezekg/xo
======
oleks
I typically fall back to perl when sed isn't quite versatile/cross-platform
enough. What do Go regular expressions offer over Perl regular expressions?
(Performance, I imagine.)

~~~
ezekg
Definitely performance. Go doesn't support negative/positive lookaheads (for
performance/efficiency reasons, apparently), so that's really the only
downside I've found with Go's regular expressions.

------
voaie
Obviously it is not line-oriented. So the selling points are? 1. custom
delimiters 2. special syntax for fallback values 3. extended regular
expression with Unicode support

More?

~~~
ezekg
Well, it can be line-oriented. It just depends on the regexp pattern and flags
specified, i.e. `/^somePattern/someFormatter/` vs
`/^somePattern/someFormatter/ms` (`m` flag to enable multi-line mode, `s` to
let `.` match `\n`). It has support for multi-line matches, as well as
matching per-line. Maybe I'm just misunderstanding you.

Go's regexp syntax:
[https://golang.org/pkg/regexp/syntax/](https://golang.org/pkg/regexp/syntax/)

~~~
voaie
By mentioning sed and line-oriented, I means you don't have to read all
content of stdin (i.e. full buffering) before matching.

~~~
ezekg
Okay yes, you're correct; xo does not buffer stdin, because the regular
expression pattern may span the entire contents.

------
ezekg
Some fun examples of what you can do with `xo`,

 __Hello world: __

    
    
    echo'Hello! My name is C3PO, human cyborg relations.' | xo '/^(\w+)! my name is (\w+)/$1, $2!/i'
        # => Hello, C3PO!
    

__A quick breakdown on the syntax: __

    
    
    echo'Hello! My name is C3PO.' | xo '/^(\w+)?! my name is (\w+)/$1, $2!/i'
        ^                              ^     ^^                       ^ ^     ^ ^
        |______________________________|     ||_______________________| |_____| |
                        |                    + Delimiter |                 |    + Flag
                        + Piped output                   + Pattern         + Formatter
    

__Boot up a Rails server with Stripe keys: __

    
    
    # ./secrets/stripe.yml
        secret_key: fE3J2Z76ry
        publishable_key qYB7936Qos
    

Then read it and boot Rails with keys set as env vars, (without logging to the
console!)

    
    
        cat secrets/stripe.yml | xo '/secret_key:\s([\w]+).*?publishable_key:\s([\w]+)/PUBLISHABLE_KEY=$1 SECRET_KEY=$2 rails s/mis' | sh
        # => PUBLISHABLE_KEY=fE3J2Z76ry SECRET_KEY=qYB7936Qos rails s
    

__View modified files in the last commit: __

    
    
    git --no-pager log -n 1 | xo'/^commit\s([^\n]+)/git diff-tree --no-commit-id --name-status -r $1/' | sh
        # =>
        # M     some-modified-file.txt
        # A     some-added-file.txt
    

Nice to put this in a script called `git-last-modified` inside of
`/usr/local/bin`, then use `git last-modified`.

 __Decode a hidden message: __

    
    
    # ./hidden-message.txt
        WITHIN THIS MULTILINE the STRING
        LIES A HIDDEN MESSAGE meaning of
        life  THAT COULD  BE is HARD  TO
        READ 42
        
        8)
    

Then read it with,

    
    
        cat hidden-message.text | xo '/([a-z0-9\)]+)/$1/' | xargs
        # => the meaning of life is 42 8)
    

__Read a config file containing SSH info: __

    
    
    # ./servers.yml
        stages:
          production:
            server: 192.168.1.1:1234
            user: user-1
          staging:
            server: 192.168.1.1
            user: user-2
    

Then read it with,

    
    
        ssh $(cat servers.yml | xo '/.*?(production):\s*server:\s+([^:\n]+):?(\d+)?.*?user:\s+([^\n]+).*/$4@$2 -p $3?:22/mis')
        # => ssh user-1@192.168.1.1 -p 1234
        

What if we didn't specify a port, i.e. like staging? Fallback values! (Notice
the ternary operator, `?:`)

    
    
        ssh $(cat servers.yml | xo '/.*?(staging):\s*server:\s+([^:\n]+):?(\d+)?.*?user:\s+([^\n]+).*/$4@$2 -p $3?:22/mis')
        # => ssh user-2@192.168.1.1 -p 22
    

Now turn that into a `~/.bashrc` function and we're golden,

    
    
        function shh() {
          ssh $(cat servers.yml | xo "/.*?($1):\s*server:\s+([^:\n]+):?(\d+)?.*?user:\s+([^\n]+).*/\$4@\$2 -p \$3?:22/mis")
        }
        

And then we can use it like,

    
    
        shh production
        # => ssh user-1@192.168.1.1 -p 1234

