1

Context

I'm a postal worker (mail sorter) and am trying to write a bash script which allows me to input an exact street address and first couple letters of a street name, and have it return the matched string which contains the route number information. I have to sort thousands of letters which can't be machined per day by looking down at the letter, then looking up at massive posters with every address in my city on them. This script could save me literal hours, so I'm trying my hardest to learn the process to get it done. I have a hobby-like background in unix/linux scripting. Not sure if regex is the solution here, or variations of grep, find, awk, sed, or all of them!

I have a text file with a list of addresses (house number ranges and street names), each on newlines, like such:

6974-7075 hwy 99: ss1
7757-8079 hwy 99: ss14
98-258 even foo st N: 15
97-257 odd foo st N: 16
21-301 foo st S: 17
15-20 foo st S: 7
bar st: 1
fake st: 31
fake pl: 77
sample dr: 89

Notice the existance of number ranges, streets with one route (no house #'s given), even and odd specifiers, road type (st, hwy, pl, dr, etc.), north(N) and south(S) indicators, and finally route info after a colon.

Current State

I have the following script that can return the strings I want as long as I input a street number which appears exactly as is in the text file:

#! /bin/bash

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

grep $civic.*$street /path/to/addresses.txt 

executing ./script.sh 7757 or ./script.sh 7757 h will return 7757-8079 hwy 99: ss14 for me. I like the full string returned here, not just the route after the colon. But, obviously, executing ./script.sh 8020 h won't return 7757-8079 hwy 99: ss14, as my code doesn't check for numbers within a range yet.

Help Needed

However, I'm looking for a way to be able to enter 8020 h and still have it return 7757-8079 hwy 9: ss14, as 8020 is within the range 7757-8079.

Furthermore, notice foo st in the text has an even and odd range with different routes, as well as N and S indicators. I'm looking for a way to be able to enter 107 f or 107 foo and have it return 97-257 odd foo st N: 16, without also returning 98-258 even foo st N: 15, as the house # is odd. The words even/odd will always be specified in the strings for those even/odd cases, so maybe a grep or search for those words in a string with a number range if the house # entered is odd? That example would also return 21-301 odd foo st S: 17 (notice foo st S) as the the house # is within the range, and the string has odd in it. I'm ok with this returning both N and S, as I won't have time to specify N or S.

I'd be extremely grateful for any help offered in my endeavor, whether a fully fleshed out answer, or a hint at getting closer. Not here to mooch, but simply ask for some help! Let me know if I can be more specific.

3
  • There is a lot of questions in your question. I think the first step is to format your addresses.txt file like for exemple a csv file to get always the same number of fields. Good luck. – ctac_ May 8 at 17:56
  • @ctac_ I thought about that, but some of the address strings have varying amounts of fields with additional info which I need to see. I guess if it's formatted to CSV awk/sed etc would be able to specifically search the first & second fields (house # & street) to return the whole string, makes sense. I'll work on reformatting the txt to CSV with house #, odd/even, street, road type, N/S, then route info fields. – YMGenesis May 8 at 19:20
  • Just some notes from years of writing programs... Clearly define your main objective as the first step. If it is not clearly defined, then you will encounter lots of problems as you break it down in to smaller sub-tasks... Which is what you do next. Refine the problem repeatedly to break it down in to smaller sub-tasks until each sub-task is simple and manageable. For example, finding something within a range: Check first if it is greater than the lower range boundary. If it is, then check if it is less than the upper range boundary. Process of elimination. – C. M. May 10 at 21:46
1
#! /bin/bash

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

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

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

The procedure:

  1. Check if the number is odd or even and prepare an exclusion string accordingly.
  2. The first grep selects lines that match the street. All lines match an empty string, so not specifying a street will make all lines match in this step.
  3. The second grep excludes entries described as "odd" or "even", using the exclusion string from the step one.
  4. awk splits each line, taking spaces and - as delimiters. If any of the two first fields is not entirely numerical, there is no range specified and the line is printed. Otherwise apparently the two first fields define a range; the number is then tested against the range and the line printed if within the range.
11
  • Amazing, works how I need. Furthermore, how would I make it so grep only looks for streets that begin with the street letters I put in? For example 20 test returns both 10-30 test rd: 1 and 10-30 fastest st: 88. I imagine making it so the grep "$street" line doesn't catch anything before $street (so it only looks for matches that start with the letters I pass in for street), but can catch anything after $street so I can still put in 20 tes and it'll give me 10-30 test rd: 1 without 10-30 fastest st: 88, or 10-50 latest cres: 22. Maybe an asterisk or a grep option? – YMGenesis May 9 at 14:53
  • This seems to work: </path/to/addresses.txt grep [^a-z]tes.*, but I can't get it to work with the variable in the script, returns a blank. EDIT: Nevermind was quoting it wrong: </path/to/addresses.txt grep "[^a-z]${street}.*". Notice anything there that might throw exceptions in some cases? – YMGenesis May 9 at 15:26
  • Ok, turns out the [^a-z] addition excludes the ability to find addresses without house #s. Any suggestions? I think using \b instead of [^a-z] solved it. Thoughts? – YMGenesis May 9 at 15:39
  • @YMGenesis grep -E "(^| )$street" instead of grep "$street". – Kamil Maciorowski May 9 at 16:06
  • ok one more thing I noticed. Not sure if I should be asking it here, or adding it to my original question. There are some string lines in the text that have single house #s + street name, AND house # ranges. ex: 1 fake st: 1 newline 2-20 fake st: 99 newline 21-99 fake st: 42 newline 100 fake st: 33. If I input 15 fak it will return the expected 2-20 fake st: 99, along with all the non-ranged house #s like 1 fake st: 1, 100 fake st: 33, resulting in 3 matching strings, where I only want 2-20 fake st: 99. Any idea on how I can have it disregard the non-ranged matches? Thanks! – YMGenesis May 9 at 18:02
1

With awk and a bash wrapper. Save it as script.sh and make it executable.

#!/bin/bash

filename="data.txt"

n="$1"       # save number from argument list
shift        # remove number from argument list
s="$@"       # save remaining argument list
s="${s:=.*}" # set regex .* as default if street is missing

awk -v number="$n" -v street="$s" '
BEGIN{
  FS="-| "   # use field separator "-" or one space to split current row
}

$0 ~ street{
  # current row contains street

  if( $1 ~ /^[0-9]+$/ && $2 ~ /^[0-9]+$/ ){
    # current row starts with a range

    if( number >= $1 && number <= $2 ){
      # number is in expected range

      if ( $3 == "odd" || $3 == "even" ){
        # string "even" or "odd" found

        if ( $3 == "odd" && number ~ /[13579]$/ ){
          # odd
          print
        }

        if ( $3 == "even" && number ~ /[24680]$/ ){
          # even
          print
        }
      } else {
        # neither "even" or "odd" found
        print
      }
    }
    # finished with current row
    next
  }
  # match but no range found in current row
  print
}
' "$filename"
0

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.