bashawksed

Put a substring of a string into a variable


I'm trying to take a substring of a string read from a file and put it in a variable. The string will look something like this:

"dn: uid=myUid,ou=People,dc=mydomain,dc=furtherdomain"

The following succeeds but is too clunky for me:

#!/bin/bash

while IFS= read -r line; do
    if [[ "$line" == *"uid="* ]]; then
        uid1=${line#*uid=}
        uid2=${uid1%,*}
        uid3=${uid2%,*}
        uid=${uid3%,*}
        echo "$uid"
    fi
done <<< "dn: uid=myUid,ou=People,dc=mydomain,dc=furtherdomain"


Solution

  • Parsing comma-separated line

    Assuming arguments are submitted in this form:

    dn: uid=myUid,ou=People,dc=mydomain,dc=furtherdomain
    

    or even

    dn: ou=People,uid=myUid,dc=mydomain,dc=furtherdomain
    dn: ou=People,dc=furtherdomain,uid=myUid,dc=mydomain
    dn: uid=myUid,ou=People,dc=mydomain,dc=furtherdomain,cn=commonname
    dn: fn=Full Name,uid=myUid,ou=People,dc=mydomain,dc=furtherdomain
    

    (Yes, the way I present her will support spaces! ;-)

    Extracting only one variable (uid) as requested

    Something like:

    #!/bin/bash
    
    IdLine="$*"
    while IFS== read -d, name val;do
        case $name in
            uid ) uid=$val ;;
        esac
    done < <(printf %s "${IdLine#*: }")
    

    Or, inspired by anubhava's answer, but able to find uid= even if not at 1st position.

    shopt -s extglob
    line='dn: fn=Full Name,uid=myUid,ou=People,dc=mydomain,dc=furtherdomain'
    uid="${line//+(*uid=|,*)}"
    

    Extracting all subvariable into an associative array.

    Under you coud use associative array to index all values:

    Warning: There are some field repetition! For sample dc are present two time!! For this I use a coma , to separate values into same field.

    #!/bin/bash
    
    IdLine="$*"
    declare -A IdArray='()'
    while IFS== read -d, name val;do
        if [[ -v IdArray[$name] ]]; then
            IdArray[$name]+=",$val"
        else
            IdArray[$name]="$val"
        fi
    done < <(printf %s "${IdLine#*: }")
    

    Then

    echo ${IdArray[uid]}
    

    You variable will look like:

    declare -p IdArray
    
    declare -A IdArray=([dc]="mydomain,furtherdomain" [ou]="People" [uid]="myUid" )
    

    or

    declare -A IdArray=([dc]="mydomain,furtherdomain" [fn]="Full Name" [ou]="People" [uid]="myUid" )
    

    Extracting all to an associative array by a function.

    parseDnLine() {
        local -n _aArray=$1
        shift
        local _idLine="$*" _nam _val
        while IFS== read -d, _nam _val; do
            if [[ -v _aArray[$_nam] ]]; then
                _aArray[$_nam]+=,${_val%$'\n'}
            else
                _aArray[$_nam]=${_val%$'\n'}
            fi
        done <<<"${_idLine#*: },"
    }
    

    Then prepare 4 associative array:

    declare -A test{1..4} # Require to declare Associative Array outside of function
    
    parseDnLine test1 dn: ou=People,uid=myUid,dc=mydomain,dc=furtherdomain
    
    parseDnLine test2 dn: ou=People,dc=furtherdomain,uid=myUid,dc=mydomain
    
    line='dn: uid=myUid,ou=People,dc=mydomain,dc=furtherdomain,cn=commonname'
    parseDnLine test3 "$line"
    
    line="dn: fn=Full Name,uid=myUid,ou=People,dc=mydomain,dc=furtherdomain"
    parseDnLine test4 $line
    declare -p test{1..4}
    

    Will produce something like:

    declare -A test1=([dc]="mydomain,furtherdomain" [ou]="People" [uid]="myUid" )
    declare -A test2=([dc]="furtherdomain,mydomain" [ou]="People" [uid]="myUid" )
    declare -A test3=([dc]="mydomain,furtherdomain" [cn]="commonname" [ou]="People" [uid]="myUid" )
    declare -A test4=([dc]="mydomain,furtherdomain" [fn]="Full Name" [ou]="People" [uid]="myUid" )
    

    Then, of course:

    echo ${test1[uid]}
    myUid
    

    New version suitable for x509 SSL Cert's DN: subject or issuer

    With some little edits, this function become suitable for parsing DN lines from X509 certificate files, by using

    parseDnLine() {
        local -n _aArray=$1
        shift
        local _idLine _nam _val _unquot
        IFS=':=' read -r _ _idLine <<<"$*"
        while IFS='=' read -rd, _nam _val; do
        read -r _nam <<<"$_nam"
        read -r _val <<<"${_val%$'\n'}"
        _unquot=${_val//\"}
        while (( ( ${#_val} - ${#_unquot} ) % 2)); do
            read -rd, _unquot
            _val+=", $_unquot"
            _unquot=${_val//\"}
        done
        printf -v _unquot '%b' "${_unquot//\\/\\x}"
        if [[ -v _aArray[$_nam] ]]; then
            _aArray["$_nam"]+=,$_unquot
        else
            _aArray["$_nam"]=$_unquot
        fi
        done <<<"$_idLine,"
    }
    

    Then

    declare -A 'test=()'
    parseDnLine test dn: uid=myUid,ou=People,dc=mydomain,dc=furtherdomain,cn=commonname
    declare -p test
    

    Still work:

    declare -A test=([dc]="mydomain,furtherdomain" [cn]="commonname" [ou]="People" [uid]="myUid" )
    

    From my local /etc/ssl/certs directory:

    declare -A 'test=()'
    parseDnLine test $(
        openssl x509 -in /etc/ssl/certs/Go_Daddy_Root_Certificate_Authority_-_G2.pem \
            -noout -subject)
    declare -p test
    
    declare -A test=([O]="GoDaddy.com, Inc." [L]="Scottsdale" [C]="US" [ST]="Arizona" [CN]="Go Daddy Root Certificate Authority - G2" )
    

    Note the quote in [O]="GoDaddy.com, Inc.".

    And from any website:

    declare -A 'test=()'
    parseDnLine test $(
         openssl x509 -noout -subject < <(
             openssl s_client <<<$'HEAD / HTTP/1.0\r\n\r' 2>&1 paypal.com:443
    ))
    declare -p test
    
    declare -A test=([jurisdictionC]="US" [O]="PayPal, Inc." [L]="San Jose" [C]="US" [jurisdictionST]="Delaware" [businessCategory]="Private Organization" [ST]="California" [serialNumber]="3014267" [CN]="paypal.com" )
    

    Then, using dunpArray from Looping over arrays, printing both index and value:

    dumpArray test 
    
    jurisdictionC   : US
    O               : PayPal, Inc.
    L               : San Jose
    C               : US
    jurisdictionST  : Delaware
    businessCategory: Private Organization
    ST              : California
    serialNumber    : 3014267
    CN              : paypal.com
    

    Finally print both issuer and subject in one operation:

    while read -r line; do
        certyp=${line%%=*};
        echo "-- ${certyp^^} --";
        declare -A 'test=()';
        parseDnLine test "$line";
        dumpArray test;
    done < <(openssl x509 -noout -subject -issuer < <(
                 openssl s_client google.com:443 <<<$'HEAD / HTTP/1.0\r\n\r' 2>&1))
    
    -- SUBJECT --
    CN: *.google.com
    -- ISSUER --
    O : Google Trust Services
    C : US
    CN: WR2