Inkstone · blog

MacBook 有线网络和无线网络自动切换

1,369 words 5 min read #macOS#Network#launchd#Shell
Categories 工具 / macOS

背景

  • MacBook同时连接有线网络和无线网络时,可以在设置中通过调整两者的上下顺序来优先选择使用哪种网络。
  • 但实际使用中,无论如何设置,只要连接了无线网络,就会优先使用无线网络。导致有线网络的连接形同虚设。
  • 为了实现有效的有线网络连接,就不得不每次连接有线网络时进行手动开关无线网。但这个方法过于麻烦,且容易忘记操作。

方案

代码

com.mine.toggleairport.plist

这是一个用于macOS的launchd配置文件,它描述了一个启动项。具体来说,这个配置文件指定了一个由 com.asb.toggleairport 标识的任务,该任务会在需要时自动启动 /Library/Scripts/toggleAirport.sh 脚本,并监视 /Library/Preferences/SystemConfiguration 目录的更改。

该脚本的作用是切换Wi-Fi的状态,启用或禁用无线网络。如果 /Library/Preferences/SystemConfiguration 目录中的某个文件发生更改,这个任务就会重新启动脚本,确保无线网络状态与系统配置一致。

在 macOS 中,launchd 是用于管理守护进程和系统服务的工具,可以在系统启动时自动启动、维护和监视它们。配置文件中的信息告诉 launchd 哪个脚本需要执行以及在哪些情况下启动和重新启动。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.asb.toggleairport</string>
<key>OnDemand</key>
<true/>
<key>ProgramArguments</key>
<array>
<string>/Library/Scripts/toggleAirport.sh</string>
</array>
<key>WatchPaths</key>
<array>
<string>/Library/Preferences/SystemConfiguration</string>
</array>
</dict>
</plist>

!com.mine.toggleairport.plist

shell脚本: toggleAirport.sh

这个Shell脚本主要用于控制Wi-Fi和以太网连接的状态,当检测到有以太网连接时,脚本会自动关闭Wi-Fi连接以使用有线网络,而当以太网连接断开时,脚本会自动打开Wi-Fi连接以恢复无线网络。该脚本的具体步骤如下:

  1. 使用networksetup命令获取当前设备的网络配置信息,包括以太网和Wi-Fi连接的名称等。
  2. 检测当前的以太网连接状态,如果有以太网连接,则关闭Wi-Fi连接,并发送一个通知。如果以太网连接断开,则打开Wi-Fi连接,并发送一个通知。
  3. 使用growlnotify或osascript命令发送通知,以告知用户当前网络连接状态的改变。
  4. 当网络连接状态发生改变时,如果脚本所在目录下存在名为statusChanged.sh的外部脚本,则会运行该脚本。
  5. 当以太网连接状态发生改变时,会将改变后的状态存储在一个名为prev_eth_on的文件中。
  6. 当Wi-Fi连接状态发生改变时,会将改变后的状态存储在一个名为prev_air_on的文件中。
  7. 最后更新以太网连接状态。
#!/bin/bash
function set_airport {
new_status=$1
if [ $new_status = "On" ]; then
/usr/sbin/networksetup -setairportpower $air_name on
touch /var/tmp/prev_air_on
else
/usr/sbin/networksetup -setairportpower $air_name off
if [ -f "/var/tmp/prev_air_on" ]; then
rm /var/tmp/prev_air_on
fi
fi
}
function growl {
# Checks whether Growl is installed
if [ -f "/usr/local/bin/growlnotify" ]; then
/usr/local/bin/growlnotify -m "$1" -a "AirPort Utility.app"
else
osascript -e "display notification \"$1\" with title \"Wifi Toggle\" sound name \"Hero\""
fi
}
# Set default values
prev_eth_status="Off"
prev_air_status="Off"
eth_status="Off"
# Grab the names of the adapters. We assume here that any ethernet connection name ends in "Ethernet"
eth_names=`networksetup -listnetworkserviceorder | sed -En 's/^\(Hardware Port: (.*Ethernet|USB 10.*), Device: (en.)\)$/\2/p'`
air_name=`networksetup -listnetworkserviceorder | sed -En 's/^\(Hardware Port: (Wi-Fi|AirPort), Device: (en.)\)$/\2/p'`
# Determine previous ethernet status
# If file prev_eth_on exists, ethernet was active last time we checked
if [ -f "/var/tmp/prev_eth_on" ]; then
prev_eth_status="On"
fi
# Determine same for AirPort status
# File is prev_air_on
if [ -f "/var/tmp/prev_air_on" ]; then
prev_air_status="On"
fi
# Check actual current ethernet status
for eth_name in ${eth_names}; do
if ([ "$eth_name" != "" ] && [ "`ifconfig $eth_name | grep "status: active"`" != "" ]); then
eth_status="On"
fi
done
# And actual current AirPort status
air_status=`/usr/sbin/networksetup -getairportpower $air_name | awk '{ print $4 }'`
# If any change has occured. Run external script (if it exists)
if [ "$prev_air_status" != "$air_status" ] || [ "$prev_eth_status" != "$eth_status" ]; then
if [ -f "./statusChanged.sh" ]; then
"./statusChanged.sh" "$eth_status" "$air_status" &
fi
fi
# Determine whether ethernet status changed
if [ "$prev_eth_status" != "$eth_status" ]; then
if [ "$eth_status" = "On" ]; then
set_airport "Off"
growl "Wired network detected. Turning AirPort off."
else
set_airport "On"
growl "No wired network detected. Turning AirPort on."
fi
# If ethernet did not change
else
# Check whether AirPort status changed
# If so it was done manually by user
if [ "$prev_air_status" != "$air_status" ]; then
set_airport $air_status
if [ "$air_status" = "On" ]; then
growl "AirPort manually turned on."
else
growl "AirPort manually turned off."
fi
fi
fi
# Update ethernet status
if [ "$eth_status" == "On" ]; then
touch /var/tmp/prev_eth_on
else
if [ -f "/var/tmp/prev_eth_on" ]; then
rm /var/tmp/prev_eth_on
fi
fi
exit 0

命令行

Terminal window
# 放置脚本并修改权限
sudo mv Downloads/toggleAirport.sh /Library/Scripts/toggleAirport.sh
chmod 755 /Library/Scripts/toggleAirport.sh
# 放置配置文件
sudo mv Downloads/com.mine.toggleairport.plist /Library/LaunchAgents/com.mine.toggleairport.plist
# 修改配置文件的权限和group
chmod 600 /Library/LaunchAgents/com.mine.toggleairport.plist
sudo chown root /Library/LaunchAgents/com.mine.toggleairport.plist
sudo chgrp wheel /Library/LaunchAgents/com.mine.toggleairport.plist
# 启动服务
sudo launchctl bootstrap system
/Library/LaunchAgents/com.mine.toggleairport.plist
# 关闭服务
sudo launchctl bootout system /Library/LaunchAgents/com.mine.toggleairport.plist