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"
7
In bash
using extglob
(extended glob) you can achieve this in a single step:
# enable extended glob
shopt -s extglob
# our input
s="dn: uid=myUid,ou=People,dc=mydomain,dc=furtherdomain"
# cleanup the string using string substitution and declare the variable
declare "${s//+(,*|* )}"
# print the new variable
echo "$uid"
myUid
Use %%
to match the longest, rather than the shortest, suffix that matches ,*
.
$ echo ${uid1}
myUid,ou=People,dc=mydomain,dc=furtherdomain
$ echo ${uid1%,*}
myUid,ou=People,dc=mydomain
$ echo ${uid1%%,*}
myUid
$ while IFS= read -r line; do
if [[ $line =~ uid=([^,]+) ]]; then
echo "${BASH_REMATCH[1]}"
fi
done <<< "dn: uid=myUid,ou=People,dc=mydomain,dc=furtherdomain"
myUid
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 bash 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.
#!/bin/bash
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#*: },"
}
declare -A test{1..4} # Require to declare Associative Array outside of function
parseDnLine test1 dn: ou=People,uid=myUid,dc=mydomain,dc=furtherdomain
declare -p test1
parseDnLine test2 dn: ou=People,dc=furtherdomain,uid=myUid,dc=mydomain
declare -p test2
line='dn: uid=myUid,ou=People,dc=mydomain,dc=furtherdomain,cn=commonname'
parseDnLine test3 "$line"
declare -p test3
line="dn: fn=Full Name,uid=myUid,ou=People,dc=mydomain,dc=furtherdomain"
parseDnLine test4 $line
declare -p test4
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" )
1
You can do this with a single grep
call if you enable PCRE (-P
) to be able to catch the property name (uid=
) in a lookbehind, then match the property value and output only the matched substring (-o
):
grep -oP '(?<=uid=)[a-zA-Z]+' <<< 'dn: uid=myUid,ou=People,dc=mydomain,dc=furtherdomain'
You can replace [a-zA-Z]+
with a different character set if your id can contain more than just letters.
Same can be achieved with sed, without a need for PCRE lookbehind — we wrap the expected uid
value in a match group, and replace the whole string to only that 1
match group:
sed -E 's/^.*uid=([a-zA-Z]+).*$/1/' <<< 'dn: uid=myUid,ou=People,dc=mydomain,dc=furtherdomain'
3