newfurniturey
newfurniturey

Reputation: 38456

Use sed to replace matched value from associative bash array

I'm using sed to reformat an input string, a portion of which I want replaced with a different string.

The input string is a date in the format of:

%Y-%m-%dT%H:%M:%S.%N%:z
Example:
2016-01-20T08:15:32.398242-05:00

My goal is to replace the month, 01 in the example above, with a string representation such as Jan.

I've defined the following array to use:

declare -A MONTHS=([01]="Jan" [02]="Feb" [03]="Mar" [04]="Apr" [05]="May" [06]="Jun" [07]="Jul" [08]="Aug" [09]="Sep" [10]="Oct" [11]="Nov" [12]="Dec")

I can't seem to get sed to use a matched group's value as the index to the MONTHS array.

What I've tried:

# straightforward sed approach
sed 's/^[0-9]\{4\}-\([0-9]\{2\}\)-.*/${MONTHS[\1]}/g'
# result: ${MONTHS[01]}

# break out of the single quotes
sed 's/^[0-9]\{4\}-\([0-9]\{2\}\)-.*/'"${MONTHS[\1]}"'/g'
# result: 

# use double quotes
sed "s/^[0-9]\{4\}-\([0-9]\{2\}\)-.*/${MONTHS[\1]}/g"
# result: 

# use double quotes *and* a hardcoded example
sed "s/^[0-9]\{4\}-\([0-9]\{2\}\)-.*/${MONTHS[\1]}, ${MONTHS[01]}/g"
# result: , Jan

Is it possible to use a matched-group value from sed as an array index in the replacement?

Note: I'm purposefully avoiding the date function because the application of this can go beyond actual dates; but, I'm definitely open to alternative approaches such as awk.

Upvotes: 3

Views: 2031

Answers (4)

Michael Back
Michael Back

Reputation: 1871

If it must be sed... Here is a "brute force" answer using the t command:

#! /bin/sed -f
s/-01-/-Jan-/; tx
s/-02-/-Feb-/; tx
s/-03-/-Mar-/; tx
s/-04-/-Apr-/; tx
s/-05-/-May-/; tx
s/-06-/-Jun-/; tx
s/-07-/-Jul-/; tx
s/-08-/-Aug-/; tx
s/-09-/-Sep-/; tx
s/-10-/-Oct-/; tx
s/-11-/-Nov-/; tx
s/-12-/-Dec-/; tx
:x

Upvotes: 0

aragaer
aragaer

Reputation: 17858

First generate sed script from your array, then execute it.

Disclaimer: not sure that I correctly used bash array in the following code. Also not sure about quotes and escaping.

for k in $(seq -w 1 12) ; do
    echo 's/^[0-9]\{4\}-'"$k-.*/${MONTHS[$k]}/;"
done | sed -f - your_file

Alternatively just use bash:

IFS=- read year mon rest <<<"$string"
string="$year ${MONTHS[$mon]} $rest"

Upvotes: 1

anubhava
anubhava

Reputation: 785631

I suggest this awk as an alternative:

s='2016-01-20T08:15:32.398242-05:00'

awk -v ms='Jan:Feb:Mar:Apr:May:Jun:Jul:Aug:Sep:Oct:Nov:Dec' 'BEGIN{
   split(ms, mths, ":"); FS=OFS="-"} {$2=mths[$2+0]} 1' <<< "$s"

Output:

2016-Jan-20T08:15:32.398242-05:00

Upvotes: 1

Diego Torres Milano
Diego Torres Milano

Reputation: 69358

First, you can convert your associative array to a string containing the months names in order

monstr=$(for k in "${!MONTHS[@]}"; do echo $k; done | sort | while read mon; do echo ${MONTHS[$mon]}; done)

then, use awk to do the heavy lifting

awk -F- -v monstr="$monstr" 'BEGIN { split(monstr, mon, " "); } { printf("%s-%s-", $1, mon[$2+0]); for (i=3; i < NF; i++) { printf("%s-", $i); } printf("%s\n", $NF);}'

That is, store the string containing the months in a varaible that you split at the beginning, then replace the second field and print all.

Upvotes: 1

Related Questions