Question

I'm making a script for geektool so that I can always see what my DNS setting's are on my desktop. It works as far as outputting the correct commands, however I'm getting a

command not found...

here's my script

#!/bin/bash
IFS='
'

getdns='networksetup -getdnsservers'
#                   [-getdnsservers networkservice]

setdns='networksetup -setdnsservers'
# usage             [-setdnsservers networkservice dns1 [dns2] [...]]

services=(`networksetup -listallnetworkservices`)



for i in ${services[*]}; do

# original setup, which returns the correct command
# [[ ! $i =~ "*" || $1 =~ "W"  ]] && echo $getdns \"$i\"

# command that fails when attempting to execute string...
#   # [[ ! $i =~ "*" || $1 =~ "W"  ]] && echo `$getdns \"$i\"`

# my attempt to concatenate the strings 
[[ ! $i =~ "*" || $1 =~ "W"  ]] && cmd="$getdns '$i'" ;echo `$cmd`

done

Here's an example output:

/Users/user/bin/netd3: line 15: networksetup -getdnsservers 'Wi-Fi': command not found

I can run which networksetup which returns /usr/sbin/networksetup, but it's not running.

What am I missing?

Was it helpful?

Solution

Storing a command in a variable as you're doing is problematic (see BashFAQ #50: I'm trying to put a command in a variable, but the complex cases always fail!). The biggest problem is that quotes are evaluated before variables are substituted, so embedding quotes in variables (e.g. the single-quotes in cmd="$getdns '$i'") doesn't do what you expect it to.

In your particular case, there's an additional problem: you set IFS to just linefeed, so when the shell expands $cmd it splits it into "words" by looking for linefeeds -- there aren't any, so it mistakes what're supposed to be arguments (-getdnsservers and 'Wi-Fi') for part of the command itself. That is, it doesn't run the command "networksetup" with arguments "-getdnsservers" and "'Wi-Fi'"; instead it tries to run "networksetup -getdnsservers 'Wi-Fi'" as the command.

I'd make several recommendations to clean this up. First, after getting the list of services, set IFS back to its usual value. This'll also mean you need to use proper quoting in the for loop (use double-quotes, and [@] instead of [*]). You also need to clean up the list to remove the header and "*" on disabled services. Finally, I prefer $() to backquotes, so I'll go ahead and change that:

saveIFS="$IFS"
IFS='
'
services=( $(networksetup -listallnetworkservices | sed '/An asterisk ([*]) denotes that a network service is disabled./d; s/^[*]//') )
IFS="$saveIFS"

for i in "${services[@]}"; do
    ...

Now, for the commands themselves, the best option depends on why you're putting them in variables in the first place. The simplest, most direct option, is to just use the commands directly:

[[ ! $i =~ "*" || $1 =~ "W"  ]] && networksetup -getdnsservers "$i"

If you want to use a shorthand for the command, use a function rather than a variable:

getdns() {
    networksetup -getdnsservers "$@"
}
...
[[ ! $i =~ "*" || $1 =~ "W"  ]] && getdns "$i"

If you need to store the entire command (including arguments) before executing it, use an array:

[[ ! $i =~ "*" || $1 =~ "W"  ]] && cmd=(networksetup -getdnsservers "$i") && "$cmd[@]"

OTHER TIPS

Your script is trying to execute the string networksetup -getdnsservers 'Wi-Fi' as a single command. Use

eval $cmd

You could also add the following at the top of your script, under #!/bin/bash:

PATH=/bin:/usr/bin:/sbin:/usr/sbin export PATH
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top