Reputation: 45390
I need a way (the most portable) in bash, to perform a search of the ~/.netrc
file, for a particular machine api.mydomain.com
and then on the next line, pull the username value.
The format is:
machine a.mydomain.com
username foo
passsword bar
machine api.mydomain.com
username boo
password far
machine b.mydomain.com
username doo
password car
So, it should matchin api.mydomain.com
and return exactly boo
from this example.
awk '/api.mydomain.com/{getline; print}' ~/.netrc
Get's me the line I want, but how do I find the username value?
Upvotes: 4
Views: 4345
Reputation: 11
The "most portable way" to do Bash:
The "most portable way" to do .netrc machine/key matching involves at least:
There is no formal specification for netrc, so measuring "portability" in terms of feature set compatibility becomes ambiguous much beyond the above list due to divergent evolution.
Several less portable syntactic and feature improvements that could be made with non-portable Bash are noted throughout.
netrc_match() { # <host list> [port list] [key list] [netrc file]
[ -n "$1" ] && NETRC_MATCH_HOSTS="$1" || ! echo "ERROR: host list required as first argument." >&2 || return 1
NETRC_MATCH_PORTS="${2-21 990}" # allow blank list
NETRC_MATCH_KEYS="${3:-login password}"
if [ -n "$4" ]; then
NETRC="$4"
elif [ -z "$NETRC" ]; then
NETRC=~/.netrc
fi
# Bash can avoid tainting the calling environment via:
# scoping variable declarations using the "local" builtin
# returning values via a Bash 4.3 "declare -n" named reference to a Bash 4.0 "declare -A" associative array
# We otherwise maintain something resembling namespace safety by verbosely prefixing variable names with the function name.
# Bash 3.1 can be set up to do case-insensitive string comparisons on NETRC_MATCH_HOSTS and NETRC_MATCH_KEYS:
#trap "$(shopt -p nocasematch)" RETURN
#shopt -s nocasematch
# Let IFS take care of all whitespace deduplication and equivalence for us:
#for NETRC_WORD in $(<"$NETRC"); do # bash 2.02 syntactic sugar
for NETRC_WORD in $( while IFS= read -r line; do printf '%s\n' "$line"; done <"$NETRC" ); do
if [ "$NETRC_MODE" = "value" ]; then
NETRC_VALUE="$NETRC_WORD"
NETRC_MODE="key" # next NETRC_WORD is a key
#if [[ "$NETRC_KEY" == machine ]]; then # bash 3.1 with nocasematch
if [ "$NETRC_KEY" = machine ]; then
# There is ambiguity about whether incomplete sets of NETRC_MATCHED_KEYS from previous matching machine stanzas should be discarded or retained.
# Here, we retain whatever we match, overwriting with whatever we find as we go, until we have a complete set.
unset NETRC_HOST_MATCHED
unset NETRC_PORT_MATCHED
unset NETRC_MACHINE_MATCHED
NETRC_HOST="${NETRC_VALUE%:*}" # filter everything after first :
NETRC_PORT="${NETRC_VALUE#*:}" # filter everything before last :
if [ "$NETRC_HOST" = "$NETRC_PORT" ]; then # if there was no :
# There is ambiguity here about whether port-free machine declarations should always match.
# Precedent is to match liberally, which we do here:
NETRC_PORT_MATCHED=y
else
for NETRC_MATCH_PORT in $NETRC_MATCH_PORTS; do
[ "$NETRC_MATCH_PORT" = "$NETRC_PORT" ] && NETRC_PORT_MATCHED=y && break
done
fi
[ -n "$NETRC_PORT_MATCHED" ] || continue
for NETRC_MATCH_HOST in $NETRC_MATCH_HOSTS; do
# netrc is ambiguous about relative-vs-absolute host matching.
# We are somewhat liberal about what we accept here (within the limitations of POSIX shell's simple string matching).
# Glob matching, DNS resolution, and libnss-style resolv.conf "search" domains are outside the scope of what POSIX shell can do.
[ "${NETRC_MATCH_HOST#"${NETRC_MATCH_HOST%?}"}" = "." ] && NETRC_MATCH_HOST="${NETRC_MATCH_HOST%.*}" # normalize to relative host to avoid butchering ip addresses
[ "${NETRC_HOST#"${NETRC_HOST%?}"}" = "." ] && NETRC_HOST="${NETRC_HOST%.*}"
# relative host comparison:
#[[ "$NETRC_MATCH_HOST" == "$NETRC_HOST" ]] && NETRC_HOST_MATCHED=y && break # bash 3.1 with nocasematch
[ "$NETRC_MATCH_HOST" = "$NETRC_HOST" ] && NETRC_HOST_MATCHED=y && break
done
[ -n "$NETRC_HOST_MATCHED" ] && [ -n "$NETRC_PORT_MATCHED" ] && NETRC_MACHINE_MATCHED=y
elif [ -n "$NETRC_MACHINE_MATCHED" ]; then
unset NETRC_TODO
for NETRC_MATCH_KEY in $NETRC_MATCH_KEYS; do
#if [[ "$NETRC_KEY" == "$NETRC_MATCH_KEY" ]]; then # bash 3.1 with nocasematch
if [ "$NETRC_KEY" = "$NETRC_MATCH_KEY" ]; then
#printf -v "NETRC_KEY_$NETRC_MATCH_KEY" "%s" "$NETRC_VALUE" # bash 3.1 eval avoidance
eval "NETRC_KEY_$NETRC_MATCH_KEY=\"\$NETRC_VALUE\""
fi
#[ -z "${!NETRC_MATCH_KEY}" ] && NETRC_TODO=y # bash 2.0 eval avoidance
[ -z "$( eval echo \"\$NETRC_KEY_"$NETRC_MATCH_KEY"\" )" ] && NETRC_TODO=y
done
[ -z $NETRC_TODO ] && return # No NETRC_MATCH_KEYS left unassigned. Return immediately to avoid processing "default" stanza.
fi
else # NETRC_MODE == "key" or unset due to being the first NETRC_WORD
NETRC_KEY="$NETRC_WORD"
case "$NETRC_KEY" in
# bash 3.1 nocasematch would apply to these:
default) NETRC_MACHINE_MATCHED=y ;; # "default" key does not have a value, and intrinsically matches
*) NETRC_MODE="value" ;; # next NETRC_WORD is a value
esac
fi
done
echo "WARNING: missing NETRC keys." >&2 # function should've already returned by this point
return 2
}
netrc_match "api.mydomain.com" "" "username"
echo "$NETRC_KEY_username"
Upvotes: 1
Reputation: 3497
Another solution to get user of password:
grep -A2 armdocker.rnd.ericsson.se ~/.config/artifactory.netrc | awk '/login/ {print $2}'
grep -A2
gives back the following 2 lines of a requested machine
Upvotes: 0
Reputation: 63
I recently stumbled upon this issue and couldn't find an answer which covers the one-line format (i.e. machine x login y password z), and inline or intra-line comments.
What I ended up with is making the file into one line with xargs (so only single spaces remain), then using grep with lookbehind (for the keyword) and lookahead (for the next whitespace or end of line)
xargs < ~/.netrc | grep -oP '(?<=machine api\.domain\.com ).*?(?=( machine)|$)' | grep -oP '(?<=login ).*?(?=\s|$)'
This could be of course developed into a function of sorts with extending the dots in the remote host variable with backslashes, printing the password as well, etc.
get_netrc_entry() {
machine_entry=$(xargs < ~/.netrc | grep -oP "(?<=machine ${1//./\\.} ).*?(?=( machine)|$)")
grep -oP '(?<=login ).*?(?=\s|$)' <<<"${machine_entry}"
grep -oP '(?<=password ).*?(?=\s|$)' <<<"${machine_entry}"
}
Upvotes: 1
Reputation: 247092
This sed is as portable as I can make it:
sed -n '
/machine[ ]\{1,\}api.mydomain.com/ {
# we have matched the machine
:a
# next line
n
# print username, if matched
s/^[ ]\{1,\}username[ ]\{1,\}//p
# goto b if matched
tb
# else goto a
ba
:b
q
}
' ~/.netrc
The whitespace in the brackets is a space and a tab character.
Looking at this with fresh eyes, this is what I would write now:
awk -v machine=api.mydomain.com '
$1 == "machine" {
if (m)
# we have already seen the requested domain but did not find a username
exit 1
if ($2 == machine) m=1
}
m && $1 == "username" {print $2; exit}
' ~/.netrc
or if you like unreadable oneliners
awk '$1=="machine"{if(m)exit 1;if($2==M)m=1} m&&$1=="username"{print $2;exit}' M=api.mydomain.com ~/.netrc
Upvotes: 1
Reputation: 306
This vanilla Bourne shell (which includes Bash, KSH, and more) function should parse any valid .netrc file: it handled everything I could think of. Invoke with netrc-fetch MACHINE [FIELD]
. For your question, it would be netrc-fetch api.mydomain.com user
.
netrc-fetch () {
cat $HOME/.netrc | awk -v host=$1 -v field=${2:-password} '
{
for (i=1; i <= NF; i += 2) {
j=i+1;
if ($i == "machine") {
found = ($j == host);
} else if (found && ($i == field)) {
print $j;
exit;
}
}
}
'
}
Upvotes: 1
Reputation: 41460
If you like to avoid using the getline
fuction use this:
awk '/api.mydomain.com/ {f=NR} f&&f+1==NR {print $2}' ~/.netrc
boo
As Ed write here: avoid using it.
http://awk.info/?tip/getline
This will find the line number of the pattern, and then
when line number is one more, print field #2
Can be shorten some to:
awk '/api.mydomain.com/ {f=NR} f&&f+1==NR&&$0=$2' ~/.netrc
or
awk 'f&&!--f&&$0=$2; /api.mydomain.com/ {f=1}' ~/.netrc
This may be the most robust way to do it.
If there are comments line or blank line after domain, other solution fails.
awk '/api.mydomain.com/ {f=1} f && /username/ {print $2;f=0}' ~/.netrc
boo
If domain is found, set flag f
. If flag f
is true and next line has username
print field #2
Upvotes: 2
Reputation: 113934
$ awk '/api.mydomain.com/{getline; print $2}' ~/.netrc
boo
To capture it in a variable:
$ name=$(awk '/api.mydomain.com/{getline; print $2}' ~/.netrc)
$ echo "$name"
boo
By default, awk splits records (lines) into fields based on whitespace. Thus, on the line username boo
, username
is assigned to field 1, denoted $1
, and boo
is assigned to field 2, denoted $2
.
Upvotes: 5
Reputation: 37083
Here's what you could do:
awk '/api.mydomain.com/{getline;print $2}' ~/.netrc ##$2 will print username
Upvotes: 0