{"id":13850312,"url":"https://github.com/ezekg/xo","last_synced_at":"2025-10-07T09:32:08.782Z","repository":{"id":79006437,"uuid":"49608446","full_name":"ezekg/xo","owner":"ezekg","description":"Command line utility that composes regular expression matches.","archived":false,"fork":false,"pushed_at":"2017-06-12T16:28:48.000Z","size":60,"stargazers_count":185,"open_issues_count":1,"forks_count":1,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-01-21T20:35:18.686Z","etag":null,"topics":["cli","cli-utilities","cli-utility","golang","regular-expression","unix"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ezekg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-01-13T23:15:14.000Z","updated_at":"2024-11-01T17:16:54.000Z","dependencies_parsed_at":"2023-06-28T17:15:32.862Z","dependency_job_id":null,"html_url":"https://github.com/ezekg/xo","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ezekg%2Fxo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ezekg%2Fxo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ezekg%2Fxo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ezekg%2Fxo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ezekg","download_url":"https://codeload.github.com/ezekg/xo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235614276,"owners_count":19018405,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","cli-utilities","cli-utility","golang","regular-expression","unix"],"created_at":"2024-08-04T20:01:05.942Z","updated_at":"2025-10-07T09:32:03.528Z","avatar_url":"https://github.com/ezekg.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# `xo`\n[![Travis](https://img.shields.io/travis/ezekg/xo.svg?style=flat-square)](https://travis-ci.org/ezekg/xo)\n\n`xo` is a command line utility that composes regular expression match groups.\n\n```bash\necho 'Hello! My name is C3PO, human cyborg relations.' | xo '/^(\\w+)! my name is (\\w+)/$1, $2!/i'\n# =\u003e\n#  Hello, C3PO!\n```\n\nYou may find yourself using `xo` to format logs into something a bit more human-readable,\ncompose together command output into a new command, or even normalize some data using\n[fallback values](#fallback-values).\n\n`xo` comes with the full power of [Go's regular expression syntax](https://golang.org/pkg/regexp/syntax/).\nMeaning, it can handle multi-line patterns, as well as any flag you want to throw at it.\n\n## Installation\nTo install `xo`, please use `go get`. If you don't have Go installed, [get it here](https://golang.org/dl/).\nIf you would like to grab a precompiled binary, head over to the [releases](https://github.com/ezekg/xo/releases)\npage. The precompiled `xo` binaries have no external dependencies.\n\n```\ngo get github.com/ezekg/xo\n```\n\n## Usage\n`xo` accepts the following syntax; all you have to do is feed it some `stdin` via\npiped output (`echo 'hello' | xo ...`) or what have you. There's no command line\nflags, and no additional arguments. Simple and easy to use.\n```\nxo '/\u003cpattern\u003e/\u003cformatter\u003e/[flags]'\n```\n\n## Examples\nLet's start off a little simple, and then we'll ramp it up and get crazy. `xo`,\nin its simplest form, does things like this,\n```bash\necho 'Hello! My name is C3PO, human cyborg relations.' | xo '/^(\\w+)?! my name is (\\w+)/$1, $2!/i'\n# =\u003e\n#  Hello, C3PO!\n```\n\nHere's a quick breakdown of what each piece of the puzzle is,\n```bash\necho 'Hello! My name is C3PO.' | xo '/^(\\w+)?! my name is (\\w+)/$1, $2!/i'\n^                              ^     ^^                       ^ ^     ^ ^\n|______________________________|     ||_______________________| |_____| |\n                |                    + Delimiter |                 |    + Flag\n                + Piped output                   + Pattern         + Formatter\n```\n\nWhen you create a regular expression, wrapping a subexpression in parenthesis `(...)`\ncreates a new _capturing group_, numbered from left to right in order of opening\nparenthesis. Submatch `$0` is the match of the entire expression, submatch `$1`\nthe match of the first parenthesized subexpression, and so on. These capturing\ngroups are what `xo` works with.\n\nWhat about the question mark? The question mark makes the preceding token in the\nregular expression optional. `colou?r` matches both `colour` and `color`. You can\nmake several tokens optional by _grouping_ them together using parentheses, and\nplacing the question mark after the closing parenthesis, e.g. `Nov(ember)?`\nmatches `Nov` and `November`.\n\nWith that, what if the input string _forgot_ to specify a greeting, but we, desiring\nto be polite, still wanted to say \"Hello\"? Well, that sounds like a great job for\na [fallback value](#fallback-values)! Let's update the example a little bit,\n```bash\necho 'Hello! My name is C3PO.' | xo '/^(?:(\\w+)! )?my name is (\\w+)/$1?:Greetings, $2!/i'\n# =\u003e\n#  Hello, C3PO!\n\necho 'My name is Chewbacca, uuuuuur ahhhhhrrr uhrrr ahhhrrr aaargh.' | xo '/^(?:(\\w+)! )?my name is (\\w+)/$1?:Greetings, $2!/i'\n# =\u003e\n#  Greetings, Chewbacca!\n```\n\nAs you can see, we've taken the matches and created a new string out of them. We\nalso supplied a [fallback value](#fallback-values) for the first match (`$1`)\nthat gets used if no match is found, using the elvis `?:` operator.\n\n(The `?:` inside of the regex pattern is called a _non-capturing group_, which is\ndifferent from the elvis `?:` operator in the formatter; a non-capturing group\nallows you to create optional character groups without capturing them into\na match `$i` variable.)\n\nNow that we have the basics of `xo` out of the way, let's pick up the pace a little\nbit. Suppose we had a text file called `starwars.txt` containing some Star Wars quotes,\n```\nVader: If only you knew the power of the Dark Side. Obi-Wan never told you what happened to your father.\nLuke: He told me enough! He told me you killed him!\nVader: No, I am your father.\nLuke: [shocked] No. No! That's not true! That's impossible!\n```\n\nand we wanted to do a little formatting, as if we're telling it as a story. Easy!\n```bash\nxo '/^(\\w+):(\\s*\\[(.*?)\\]\\s*)?\\s*([^\\n]+)/$1 said, \"$4\" in a $3?:normal voice./mi' \u003c starwars.txt\n# =\u003e\n#   Vader said, \"If only you knew the power of the Dark Side. Obi-Wan never told you what happened to your father.\" in a normal voice.\n#   Luke said, \"He told me enough! He told me you killed him!\" in a normal voice.\n#   Vader said, \"No, I am your father.\" in a normal voice.\n#   Luke said, \"No. No! That's not true! That's impossible!\" in a shocked voice.\n```\n\nOkay, okay. Let's move away from Star Wars references and on to something a little\nmore useful. Suppose we had a configuration file called `servers.yml` containing\nsome project information. Maybe it looks like this,\n```yml\nstages:\n  production:\n    server: 192.168.1.1:1234\n    user: user-1\n  staging:\n    server: 192.168.1.1\n    user: user-2\n```\n\nNow, let's say we have one of these configuration files for every project we've ever\nworked on. Our day to day requires us to SSH into these projects a lot, and having\nto read the config file for the IP address of the server, the SSH user, as well as\nany potential port number gets pretty repetitive. Let's automate!\n```bash\nxo '/.*?(production):\\s*server:\\s+([^:\\n]+):?(\\d+)?.*?user:\\s+([^\\n]+).*/$4@$2 -p $3?:22/mis' \u003c servers.yml\n# =\u003e\n#  user-1@192.168.1.1 -p 1234\n\n# Now let's actually use the output,\nssh $(xo '/.*?(staging):\\s*server:\\s+([^:\\n]+):?(\\d+)?.*?user:\\s+([^\\n]+).*/$4@$2 -p $3?:22/mis' \u003c servers.yml)\n# =\u003e\n#  ssh user-2@192.168.1.1 -p 22\n```\n\nSet that up as a nice `~/.bashrc` function, and then you're good to go:\n```bash\nfunction shh() {\n  ssh $(xo \"/.*?($1):\\s*server:\\s+([^:\\n]+):?(\\d+)?.*?user:\\s+([^\\n]+).*/\\$4@\\$2 -p \\$3?:22/mis\" \u003c servers.yml)\n}\n\n# And then we can use it like,\nshh production\n# =\u003e\n#  ssh user-1@192.168.1.1 -p 1234\n```\n\nLastly, what about reading sensitive credentials from an ignored configuration\nfile to pass to a process, say, `rails s`? Let's use Stripe keys as an example\nof something we might not want to log to our terminal history,\n```bash\ncat secrets/*.yml | xo '/test_secret_key:\\s([\\w]+).*?test_publishable_key:\\s([\\w]+)/PUBLISHABLE_KEY=$1 SECRET_KEY=$2 rails s/mis' | sh\n```\n\nPretty cool, huh?\n\n## Fallback values\nYou may specify fallback values for matches using the elvis operator, `$i?:value`,\nwhere `i` is the index that you want to assign the fallback value to. The fallback\nvalue may contain any sequence of characters, though anything other than letters,\nnumbers, dashes and underscores must be escaped; it may also contain other match\ngroup indices if they are in descending order e.g. `$2?:$1`, not `$1?:$2`.\n\n## Delimiters\nYou may substitute `/` for any delimiter. If the delimiter is found within your pattern\nor formatter, it must be escaped. If it would normally be escaped in your pattern\nor formatter, it must be escaped again. For example,\n\n```bash\n# Using the delimiter `|`,\necho 'Hello! My name is C3PO, human cyborg relations.' | xo '|^(\\w+)?! my name is (\\w+)|$1, $2!|i'\n\n# Using the delimiter `w`,\necho 'Hello! My name is C3PO, human cyborg relations.' | xo 'w^(\\\\w+)?! my name is (\\\\w+)w$1, $2!wi'\n```\n\n## Regular expression features\nPlease see [Go's regular expression documentation](https://golang.org/pkg/regexp/syntax/)\nfor additional usage options and features.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fezekg%2Fxo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fezekg%2Fxo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fezekg%2Fxo/lists"}