4

I have a script that we use on Ubuntu (Linux) and I'd like to convert it to be used on both Ubuntu (Linux) and MacOS X. grep on Linux is different than grep on FreeBSD (i.e. MacOS X); grep on MacOS X doesn't support the -P option. Unfortunately, using the -E option on both platforms doesn't give the same results. Consider the following code that works on Linux:

wip_scenarios=$(grep -oP "^\d+ scenarios?" log/report.log | grep -oP "\d+")
echo "\n"
echo $wip_scenarios

This returns a 0 on Linux. Replacing all the -P with -E makes this work on MacOS X, but on Linux, this just returns a null which doesn't help the rest of my script when I use conditionals like this:

if [ $wip_scenarios != 0 ];then

One solution is to put a flag at the front and use the appropriate option set depending on the platform, but I was hoping for a cross-platform solution. Is there a way to do this?

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Son of the Wai-Pan
  • 12,371
  • 16
  • 46
  • 55
  • 2
    BTW, `[ $wip_scenarios != 0 ]` is buggy itself; needs to be `[ "$wip_scenarios" != 0 ]` -- the quotes aren't optional; leaving them off can lead to some interesting bugs depending on the exact value. – Charles Duffy Jun 02 '15 at 00:02
  • 1
    On a different note, by the way -- do you want the final `s` in `scenarios` to be optional? That's what the trailing `?` is doing; if you want to match a literal `?`, the best way to do that (IMHO) is a character class: `[[:digit:]]+ scenarios[?]` – Charles Duffy Jun 02 '15 at 00:03
  • 1
    Also, don't use `""` when you can use `''`; using `"\d"` is not the same as `'\d'`, as in double-quotes, the sequence is expanded by some shells, replaced with just `"d"`. – Charles Duffy Jun 02 '15 at 00:09
  • See http://pubs.opengroup.org/onlinepubs/009604499/utilities/grep.html re: `grep -o` also not being part of the standard. – Charles Duffy Jun 02 '15 at 00:45
  • Thanks for all your tips Charles. I _did_ want the final 's' in scenarios to be optional, so the `?` is intentional. – Son of the Wai-Pan Jun 02 '15 at 01:28

1 Answers1

6

For the regex you gave here, this is simple: Change \d to [[:digit:]].

Thus:

wip_scenarios=$(grep -Eo '^[[:digit:]]+ scenarios[?]' <report.log | grep -Eo '[[:digit:]]+')

If your script starts with #!/bin/bash (and thus will only ever be run with bash), I'd also consider skipping the dependency on the non-standard extension grep -o, and instead depending on bash itself to separate out the numbers you care about:

# This works with any POSIX-compliant grep, but requires that the shell be bash
wip_scenarios_re='([[:digit:]]+) scenarios[?]'
wip_scenarios_line=$(grep -E '^[[:digit:]]+ scenarios[?]' <report.log)
[[ $wip_scenarios_line =~ $wip_scenarios_re ]] && {
  wip_scenarios=${BASH_REMATCH[1]}
}
wjandrea
  • 28,235
  • 9
  • 60
  • 81
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • I'm always frustrated by the difficulty in reading bash scripts. I could understand most of what you wrote, but could you translate this for me? `[[ $wip_scenarios_line =~ $wip_scenarios_re ]] && { wip_scenarios=${BASH_REMATCH[1]} }` Something like: "If wip_scenarios_line matches the given regexp, wip_scenarios_re, then..."? Why the `&&`? What does that mean? – Son of the Wai-Pan Jun 02 '15 at 01:38
  • It means the same thing it means in C or Java -- it's a short-circuiting boolean "and". `a && b` has the side effect of only evaluating `b` if `a` is true, so it serves double duty as a conditional, itself evaluating to a value that represents whether both commands succeeded.. – Charles Duffy Jun 02 '15 at 03:03
  • 1
    Is this a common bash script idiom? I'm just curious, because while I understand it, it doesn't come across as particularly readable. – Son of the Wai-Pan Jun 03 '15 at 01:05
  • Which part? There are lots of idioms in here. That said, the things that might be surprising -- like assigning the regex to a string before running it -- are done for reasons (in that particular case, compatibility with older versions of bash back before `=~` syntax stabilized). – Charles Duffy Jun 03 '15 at 01:26
  • Oh -- you mean `&&`. Yes, that's very idiomatic, not just in bash but in POSIX shells in general. (Now, there are also related antipatterns -- folks who use `a && b || c` to mean `if a; then b; else c; fi`, for instance, are inviting bugs. Incidentally, that same practice used to be a problem back in the Python world as well, back before one-liner conditional syntax was introduced there). – Charles Duffy Jun 03 '15 at 01:27
  • @Charles What's the advantage of using `$condition && { ... }` instead of `if $condition; then ...; fi`? Isn't the if-statement more flexible? Like say you needed to add an `elif` or `else` clause, it'd require less rewriting. Or if you needed to add another `&&` condition, it'd be easier to see where the conditions end and the consequent begins. – wjandrea Jul 22 '22 at 21:41
  • 1
    You're absolutely right: `if` is more flexible (and harder to inadvertently misuse); the sole advantage is terseness. – Charles Duffy Jul 23 '22 at 13:03