1

Context

This is a follow-up question to a question I asked previously. A new detail/issue arose that I wasn't aware of prior to getting some amazing help from Kamil Maciorowski and Cyrus. I chose Kamil Maciorowski's answer due to his explanation and succinctness, though both answers achieved what I was looking for at the time. The precise reason for this script is explained in my previous question.

What it is

Kamil Maciorowski's code, called script.sh:

#! /bin/bash

civic="$1"
street="$2"

if [ "$((civic%2))" = 1 ]; then
   exclude=" even "
else
   exclude=" odd "
fi

</path/to/addresses.txt grep -E "(^| )$street" \
   | grep -v "$exclude" \
   | awk -F '[ -]' -v civic="$civic" '
      {if ($1 !~ /^[0123456789]*$/ || $2 !~ /^[0123456789]*$/) print
       else if (civic>=$1 && civic<=$2) print}
     '

This code allows me to pass in a house number and a street name. It will search addresses.txt and return the proper string by checking address ranges, as well as addresses without any house number preceding the street name. Here's a sample of addresses.txt demonstrating variation cases (anonymized):

1 fastest rd S: 99
2-58 fastest rd N: 98
42 fake st: ss12
1 test st: 1
2-199 test st: 2
200-300 even test st: 22
301-399 odd test st: 33
example dr N: ss5
example dr S: 226
956 sample rd N: 53
976-998 even sample rd N: 54
340-500 even sample rd S: ss11
401-487 odd sample rd S: 45

Using that data, I can run ./script.sh 1 fas, and get the following output, which is perfect:

1 fastest rd s: 99

Another perfect example, ./script.sh 42 fak:

42 fake st: ss12

Another good example, ./script.sh 20 ex:

example dr N: ss5
example dr S: 226

Here it returns both example dr N and S, which is important for me to see, and is how I need it to behave.

What I'm having trouble with

In my original question I neglected to include strings in my addresses.txt sample with just one house number, instead of a range (ex: 1 test st: 1). To set up our data for this portion, here are some relevant strings from my above addresses.txt sample:

1 fastest rd S: 99
2-58 fastest rd N: 98
1 test st: 1
2-199 test st: 2
956 sample rd N: 53
976-998 even sample rd N: 54
340-500 even sample rd S: ss11
401-487 odd sample rd S: 45

In the script's current state (What it is), running ./script.sh 89 tes outputs:

1 test st: 1
2-199 test st: 2

Notice the 1 test st: 1 line. I'd like it to be able to return only 2-199 test st: 2, as that matches my search of 89 tes more precisely.

Another example, ./script.sh 483 sam:

956 sample rd N: 53
401-487 odd sample rd S: 45

Notice it successfully recognizes 483 as odd, and matches it with 401-487 odd sample rd S: 45 range, instead of also including the 340-500 even sample rd S: ss11 range. However, it also returns 956 sample rd N: 53, which does not match my search.

My attempts to solve the issue

Kamil Maciorowski hinted that the awk portion of the script could be altered to "seek for the first not-entirely-numerical field and therefore know if there's a range, a single value or nothing before the street name." I tried adding another else if line to awk which would try to find a number, and print if the house # was alone, and followed by a space. I added else if (civic =~ /^[0123456789]\s$/) print}:

</path/to/addresses.txt grep -E "(^| )$street" \
   | grep -v "$exclude" \
   | awk -F '[ -]' -v civic="$civic" '
      {if ($1 !~ /^[0123456789]*$/ || $2 !~ /^[0123456789]*$/) print
       else if (civic>=$1 && civic<=$2) print
       else if (civic =~ /^[0123456789]\s$/) print}
     '

Not entirely surprised I can't stop it from throwing syntax errors, as this kind of expression is new to me. I tried flipping the ($1 !~ /^[0123456789]*$/ || $2 !~ /^[0123456789]*$/) and (civic>=$1 && civic<=$2) lines, which returned only 340-500 even sample rd S: ss11 when searching for 480 sam. However a search for 956 sam did not produce 956 sample rd N: 53.

I also tried to add another if exclusion at the start of script.sh, but I realized single house numbers aren't constant like odd or even.

Any further help or suggestions would be greatly appreciated. I know I need to somehow exclude single house numbers when a range is successfully found, but I'm have real trouble wrapping my head around an approach to do so.

8
  • Will there ever been an overlap between the range and the single numbers (or even a different range)? e.g. 3 fastest rd: 99 1-58 fastest rd: 98. In general there are two ways to approach this, (1) Have clean data and a simple program, or (2) have dirty data and a complicated program. For this case I think clean data is a good approach, so perhaps you can change your "1 test st: 1" line to "1-1 test st: 1" so there are never any cases where you don't have a range (even if the range is just 1 long). This is not a good approach if the data changes frequently so can you tell us this as well? – icarus May 9 at 22:58
  • Yes there will be an overlap in some cases. Kamil did suggest the 1-1 range solution. My addresses.txt data is around 2000 lines long, so I'd have to go in and change each single number to a single 1-1 range. I agree with the philosophy you mention, clean and simple. Would just take a while to change the data. But if it works, it works. The data itself doesn't change much. It's supposed to be a database of addresses which specify which route a letter carrier delivers to (the number after the colon), so it doesn't change much at all. Maybe once a year or less. Little changes here and there. – YMGenesis May 9 at 23:12
  • continued: But, I think if it makes more sense to add 1-1 ranges (two people have suggested it now), maybe I'll just bite the bullet and start changing the data. I appreciate the input. – YMGenesis May 9 at 23:16
  • sed '/^[0-9]* /s/^\([0-9]*\)/\1-\1/' addresses.txt> newaddresses.txt might be a good starting point. – icarus May 10 at 4:44
  • @YMGenesis changing $1 if it doesn't include a range is a good idea - but you don't have to change all your input files. just do something like $1 !~ /-/ {$1 = $1 "-" $1}, or $1 ~ /^[[:digit:]]+$/ {$1 = $1 "-" $1} in your awk script. – cas May 10 at 6:33
1

This modified script adds support for single numbers:

#! /bin/bash

civic="$1"
street="$2"

if [ "$((civic%2))" = 1 ]; then
   exclude=" even "
else
   exclude=" odd "
fi

</path/to/addresses.txt grep -E "(^| )$street" \
   | grep -v "$exclude" \
   | awk -F '[ -]' -v civic="$civic" '
      {if ($1 !~ /^[0123456789]*$/) print
       else if ($2 !~ /^[0123456789]*$/) {if (civic==$1) print}
       else if (civic>=$1 && civic<=$2) print}
     '

The awk code now considers three cases (remember filtering by street has already been done by grep; awk filters by civic):

  1. If the first field is not entirely numerical then this means there is no range nor number and the line matches.
  2. Otherwise if the second field is not entirely numerical then this means the entry begins with single number. In this case the line matches iff civic matches the number.
  3. If neither the first nor the second case, the assumption is made it's a range. The line matches iff civic is within the range.
2
  • The answer is community wiki because I only improved my original code slightly, the code that has already brought me some reputation. – Kamil Maciorowski May 10 at 5:29
  • The breakdowns you give helps me understand it A LOT better, and it does exactly what I was looking for. Thank you, accepting this community wiki answer. – YMGenesis May 10 at 14:19

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.