I have a file cities like this:

[1598] San Diego, US (inactive)
[4517] St Louis, US (inactive)
[6346] Orlando, US (inactive)

I want to cut out the city names, so that I have:

San Diego
St Louis

This is the best I could come up with:

cut -d ',' -f1 cities | cut -d ']' -f2

But that still leaves me with a space before the names. Is there a cut like command that I can use that accept delimiters of several characters so I can cut on ]?

  • 1
    tr is useful for deleting characters you don't want.
    – LawrenceC
    Apr 22, 2011 at 14:50
  • If you try the code in people's answers, you will see three different outputs. This suggests your question was not 100% clear. Does "cut out" mean remove or select? Do you want the (inactive) status or not? Please provide sample output.
    – Mikel
    Apr 23, 2011 at 3:42
  • @Mikel - Considering I'm using cut to cut things out and you can see the intent of the failed example I have, it should be fairly clear in the context. I will provide sample out though to clear it up further. :)
    – Kit Sunde
    Apr 23, 2011 at 4:25
  • No, not really. I changed one sentence in your question to "print only the city names", because it was your use of the word "cut" that was unclear to me. Is my change correct?
    – Mikel
    Apr 23, 2011 at 5:04
  • 1
    @Kit Sunde: With the sample output, it's certainly understandable. The title is cute. "cut out" makes me think of what happens when you press Ctrl+X, which is why I suggested the change, but it's your question. Downvoting would be silly when it's just a simple disagreement.
    – Mikel
    Apr 23, 2011 at 7:13

4 Answers 4


Awk (also check Awk Info) is beautiful with that sort of question. Try:

awk -F'[],] *' '{print $2}' cities

This defines a field separator -F as [],] * - which means one occurence of either a closing square bracket or a comma, followed by zero or any number of spaces. Of course you can change that to suit any requirement. Read up on regular expressions.

Once the line is split, you can do what you want with the split result. Here, I decided to print out the second field only with print $2. Note that it is important to use single quotes around the awk instructions otherwise $2 gets substituted by the shell.

  • 2
    ] is not an angle bracket. Angle brackets are <>. [] are "square brackets", or just "brackets".
    – cjm
    Apr 22, 2011 at 18:54
  • I think you need to escape that closing bracket, unless I actually do need to go read up on my regular expressions.
    – Kit Sunde
    Apr 22, 2011 at 18:58
  • @cjm - Maybe he's German: news.ycombinator.com/item?id=1181243 :)
    – Kit Sunde
    Apr 22, 2011 at 19:01
  • 1
    @cjm, sorry I meant to say square bracket, typed a little too fast. @Kit, I am not German. You do not want to escape the inner closing bracket (it would serve no purpose), but it must be the first character in the range.
    – asoundmove
    Apr 22, 2011 at 19:20

You can modify the last cut in your pipeline to this:

cut -d ' ' -f2-

The above means that field separator is whitespace, and we want to select all fields starting from the second. The complete sequence becomes:

cut -d ',' -f1 cities | cut -d ' ' -f2-

For more complex parsing, you should use sed(1):

sed -e 's/\[[0-9]\+\] \([^,]\+\),.*/\1/' cities

Or using -r to simplify the regular expression, as suggested by pepoluan:

sed -re 's/\[[0-9]+\] ([^,]+),.*/\1/' cities
  • 2
    +1. you can also use -r to prevent escaping advanced regex chars, greatly simplifying the regex pattern
    – pepoluan
    Apr 22, 2011 at 15:27

I normally use Perl when things get too hard for sed and grep.

There are a number of ways you could write it in Perl. For example, you might prefer it to be fast, or you might prefer it to handle slight unexpected problems in the input (e.g. two spaces where one was expected).

One obvious way (assumes id is numeric, city is alphabetic, status is alphabetic):

while (<>) {
    if (/^\[\d+\] (\w+(?: \w+)*), \w+ \(\w*\)$/) {
        my $city = $1;
        print "$city\n";

Or slower but more permissive (does more backtracking):

while (<>) {
    if (/^.*\]\s+(.*),.*$/) {
        my $city = $1;
        print "$city\n";

Or faster (field stops at first occurrence of closing bracket):

while (<>) {
    if (/^\[[^]]*\] ([^,]*), \S+ \([^)]*\)$/) {
        my $city = $1;
        print "$city\n";

From the command line rather than a script, you could use the -n option, which basically adds the while (<>) { BLOCK } loop:

perl -ne '/^\[[^]]*\] ([^,]*), \S+ \([^)]*\)$/ and print $1, "\n";' cities

or if you want the usage to resemble cut, you can use the -F option, which is similar to awk's -F option, for example:

perl -a -n -F'/[],]\s+/' -e 'print $F[1], "\n"' cities

This way obviously assumes that no field will contain any of the delimiters.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.