news 2026/7/29 11:50:58

Shell脚本自动化交互:管道、Here Document与Expect实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Shell脚本自动化交互:管道、Here Document与Expect实战指南

1. 项目概述:自动化交互的刚需与挑战

在运维和开发工作中,我们经常需要编写Shell脚本来完成重复性的系统管理、软件部署或数据备份任务。一个典型的痛点在于,很多命令行工具,比如sudosshscppasswd,甚至是某些数据库客户端或安装程序,在执行过程中会交互式地要求用户输入密码。当脚本需要批量、无人值守地运行时,这种交互式提示就成了自动化流程中的“拦路虎”。手动输入密码不仅效率低下,更违背了自动化的初衷。

因此,“Shell脚本自动输入密码”这个需求应运而生。它不是一个炫技的功能,而是提升运维效率、实现持续集成/持续部署(CI/CD)流水线、以及构建健壮的后台任务所必须掌握的核心技能。简单来说,它的目标就是让脚本能够模拟人类,在需要的时候自动、安全地提供认证凭据,让流程顺畅地跑下去。

然而,实现自动输入密码远非一个echo “mypassword”那么简单。它涉及到安全性、可靠性、可移植性以及脚本健壮性等多个维度的考量。密码明文写在脚本里是绝对的安全禁忌;不同的工具对标准输入的处理方式可能不同;在复杂的多步交互中,如何精准地响应提示也是一个挑战。网络上相关的讨论和代码片段很多,但往往只给出片段,缺乏系统性的对比和深入的原理解析,导致很多朋友在实践时踩坑。

今天,我就结合自己多年的实战经验,为你系统梳理Shell脚本中自动输入密码的三种主流方式:管道与重定向Here Document,以及Expect工具。我会详细拆解每种方法的原理、适用场景、具体写法以及那些容易被忽略的“坑”,并分享如何根据你的实际需求选择最合适的方法。无论你是刚接触Shell的新手,还是希望优化现有脚本的老手,这篇文章都能给你带来直接的帮助。

2. 核心方案深度解析与选型指南

在深入代码之前,我们必须先建立正确的认知框架。自动输入密码的本质是程序间的自动化交互。发起交互的程序(如sudo)我们称为“客户端”或“目标命令”,而我们的脚本需要扮演一个“自动应答机”的角色。根据交互的复杂度和安全要求,我们可以选择不同复杂度的“应答机制”。

2.1 方案一:管道与重定向——简单场景的利器

这是最基础、最直观的方法。它的核心思想是利用Shell的输入输出重定向功能,将预先准备好的密码文本,通过标准输入(stdin)传递给需要密码的命令。

工作原理:在Unix/Linux系统中,每个进程默认打开三个文件描述符:标准输入(0)、标准输出(1)和标准错误(2)。echo “password”命令会将字符串输出到标准输出。管道|可以将前一个命令的标准输出连接到后一个命令的标准输入。而<重定向符则可以直接将一个文件的内容作为标准输入提供给命令。

适用场景:适用于那些从标准输入读取一次密码后就结束交互的简单命令。典型代表是sudo -Ssshpass的铺垫(虽然sshpass本身是另一种封装)。

优势与局限

  • 优势:实现简单,无需额外工具,兼容性极好。
  • 局限
    1. 安全性最差:密码明文出现在命令行或脚本中,通过ps auxhistory命令可能被窥探。
    2. 交互能力弱:只能应对单次、即时的密码提示。如果目标程序先输出一些信息再要密码,或者密码错误后重试,这种方法就会失败。
    3. 对某些程序无效:有些程序(如passwd)出于安全考虑,会直接打开终端设备(/dev/tty)来读取密码,而不会从标准输入读取,这使得管道重定向对其无效。

重要安全提示:在任何生产环境或共享服务器上,绝对避免将明文密码写入脚本。即使你觉得自己删除了,它也可能存在于版本历史、备份或进程列表中。后续我们会讨论如何相对安全地处理密码。

2.2 方案二:Here Document——结构化输入的优雅方式

Here Document(常写作<<)是Shell的一种特殊重定向,它允许在脚本中内嵌一段文本,并将其作为命令的标准输入。它比管道更清晰,尤其适合需要输入多行内容的情况。

工作原理command <<EOF告诉Shell,将接下来直到独立一行“EOF”标记(可以是任意字符串,常用EOF或END)之间的所有文本,作为标准输入传递给command

适用场景:除了适用于方案一的场景外,还特别适合需要自动化交互式命令行工具的场景,这些工具会连续提出多个问题,例如某些老式安装程序、数据库交互界面(如mysql命令行客户端在特定参数下)或文本菜单配置工具。

优势与局限

  • 优势:语法清晰,输入内容在脚本中格式整齐,易于维护多行交互。比管道更直观地表示“这是输入数据”。
  • 局限
    1. 安全性同方案一:密码同样以明文形式嵌入脚本。
    2. 依然无法应对复杂交互:虽然能输入多行,但它仍然是“一次性”将所有内容发送出去。如果目标程序根据之前的回答动态改变后续问题,Here Document无法做出条件响应。
    3. 同样受限于/dev/tty:对于直接读取终端的程序无效。

2.3 方案三:Expect——自动化交互的终极武器

当面对复杂的、多轮的、有条件分支的交互时,前两种方法就力不从心了。这时就需要Expect。Expect本身是一个独立的脚本语言,或者说是一个强大的工具,它基于Tcl,专门设计用来与交互式程序进行“对话”。

工作原理:Expect脚本的核心是“期待-发送”循环。它运行一个目标程序(如ssh),然后监视这个程序的输出。当输出匹配到预设的“期待”字符串(比如“password:”)时,就自动“发送”相应的回应(比如密码)。它可以处理超时、匹配失败、条件判断等复杂逻辑。

适用场景:所有需要自动化的交互场景,特别是:

  • SSH自动登录(处理不同的提示,如Are you sure you want to continue connecting (yes/no)?password:)。
  • 自动配置交换机、路由器等网络设备(通过telnet或console)。
  • 安装过程中需要多次确认的软件。
  • 与任何基于文本菜单的古老系统交互。

优势与局限

  • 优势
    1. 功能强大:可以处理任意复杂的交互流程,支持正则表达式匹配,具备完整的编程能力(变量、循环、条件分支)。
    2. 相对安全:密码可以存储在单独的加密文件或从环境变量读取,避免明文出现在主脚本中。
    3. 可靠性高:通过精确匹配提示词,避免了因输出信息变化(如欢迎标语)导致的输入时机错误。
  • 局限
    1. 需要额外安装:大多数系统默认不安装expect,需要手动安装(如apt-get install expectyum install expect)。
    2. 学习成本:需要学习基本的Expect语法(虽然很简单)。
    3. 脚本稍显冗长:对于简单任务,用Expect有点“杀鸡用牛刀”。

选型决策速查表

特性/场景管道/重定向Here DocumentExpect
实现复杂度极低中到高
交互复杂度单次,简单提示单次或固定多次提示任意复杂交互
安全性差(明文)差(明文)中(可分离凭证)
依赖要求需安装expect包
典型用例sudo -S,简单工具自动化安装脚本问答SSH自动登录,网络设备配置

3. 三种方式的实战详解与避坑指南

了解了原理和选型,我们进入实战环节。我会为每种方式提供详细的代码示例,并附上关键的注意事项和避坑技巧。

3.1 管道与重定向实战

基础用法示例:

#!/bin/bash # 示例1:使用echo和管道 echo “mySecurePassword” | sudo -S apt-get update # 示例2:使用printf和管道(更推荐,避免换行符问题) printf “mySecurePassword\n” | sudo -S apt-get upgrade -y # 示例3:使用重定向来自文件(密码在文件中) echo “mySecurePassword” > /tmp/pass.txt chmod 600 /tmp/pass.txt # 关键:设置文件权限! sudo -S apt-get autoremove -y < /tmp/pass.txt rm -f /tmp/pass.txt # 使用后立即删除

关键参数解析:

  • sudo -S:这个-S选项是关键,它告诉sudo从标准输入读取密码,而不是尝试打开终端。没有这个选项,管道方法对sudo无效。
  • printf “password\n”:比起echoprintf能更精确地控制输出格式。这里显式添加换行符\n,模拟用户敲击回车。有些程序可能不需要换行符,但大多数需要,加上更保险。

避坑技巧与注意事项:

  1. 密码换行符:这是最常见的坑。很多程序在读取密码时,会把换行符当作输入结束的标记。如果你用echo “password” | commandecho默认会在输出末尾添加换行符,这通常是正确的。但为了绝对明确,使用printf “password\n”是更好的习惯。反之,极少数程序可能只需要字符不需要回车,这时可以用printf “password”echo -n “password”
  2. 权限管理:如果密码存储在临时文件中,务必使用chmod 600 file将文件权限设置为仅所有者可读写,防止其他用户窥探。
  3. 历史记录:在命令行中直接运行echo “pass” | sudo -S cmd,密码可能会保存在Shell的历史记录(~/.bash_history)中。在脚本中执行则通常不会。安全起见,可以在命令前加一个空格(如果Shell配置了HISTCONTROL=ignorespace),或者临时禁用历史记录。
  4. 作用域问题:管道和重定向只影响紧接其后的一条命令。如果你需要在一段脚本中多次使用同一个密码,需要每次都传递。

3.2 Here Document实战

基础用法示例:

#!/bin/bash # 示例1:自动化sudo命令(与管道类似) sudo -S apt-get install nginx <<EOF mySecurePassword EOF # 示例2:自动化一个交互式配置工具(例如一个假想的`setup-tool`) setup-tool <<CONFIG_END John Doe johndoe@example.com mySecurePassword y CONFIG_END # 假设setup-tool依次询问:姓名、邮箱、密码、是否确认(y/n)

高级用法:变量替换Here Document 内的内容支持Shell变量替换,这让你可以动态构造输入。

#!/bin/bash USER_NAME=“johndoe” USER_PASS=“mySecurePassword” # 仍然不推荐明文! sudo -S useradd -m $USER_NAME <<ADD_USER $USER_PASS $USER_PASS ADD_USER # 模拟为新增用户设置密码(passwd命令会要求输入两次) # 注意:实际`passwd`命令可能不从stdin读,此处仅为演示语法。

避坑技巧与注意事项:

  1. 结束标记:结束标记(如EOF)必须顶格写在一行的开头,前后不能有任何空格。这是最常见的语法错误来源。
  2. 禁止变量替换:如果你希望Here Document内的内容原样输出,不进行变量或命令替换,应该将开始标记用引号括起来,如<<‘EOF’<<\EOF
    cat <<‘LITERAL’ 我的密码是 $PASSWORD, 这个变量不会被替换。 当前路径是 `pwd`, 这个命令也不会执行。 LITERAL
  3. 缩进问题:默认情况下,Here Document正文前的缩进(Tab)也会被作为输入内容的一部分。如果为了脚本美观需要缩进,可以使用<<-并配合Tab(而非空格)来缩进结束标记,Shell会忽略正文行和结束标记前的Tab。
    function my_task() { sudo -S command <<-PASS_EOF mySecurePassword PASS_EOF # 这一行必须以Tab开头,不能是空格 }
  4. 输入缓冲:与管道一样,所有内容是一次性发送的。如果目标程序在收到第一行输入后就暂停了(例如等待一个确认信号),后续的行可能会被积压在缓冲区,导致交互错乱。这超出了Here Document的能力范围,需用Expect。

3.3 Expect脚本实战

Expect的语法需要一点学习,但基本模式非常固定。

基础模板与示例:首先,确保系统安装了expect:sudo apt-get install expectsudo yum install expect

示例1:自动化SSH登录并执行命令

#!/usr/bin/expect # 注意:这不是Bash脚本,首行是expect解释器 set timeout 30 # 设置超时时间为30秒 set host “192.168.1.100” set username “root” set password “mySshPassword” spawn ssh $username@$host expect { “*yes/no*” { send “yes\r”; exp_continue } # 处理首次连接确认 “*password:*” { send “$password\r” } # 发送密码 } # 登录成功后,期待看到命令提示符,然后执行命令 expect “*#*” { send “ls -la\r” } expect “*#*” { send “exit\r” } # 执行完命令后退出 expect eof # 等待spawn的进程结束

示例2:一个更健壮、带错误处理的版本

#!/usr/bin/expect set timeout 10 set host [lindex $argv 0] ; # 从命令行第一个参数获取主机 set user [lindex $argv 1] ; # 第二个参数获取用户 set pass [lindex $argv 2] ; # 第三个参数获取密码(仍不安全,但比写死好) spawn ssh $user@$host expect { timeout { puts “连接超时”; exit 1 } “*Permission denied*” { puts “密码错误或权限被拒”; exit 1 } “*Connection refused*” { puts “连接被拒绝,检查主机和端口”; exit 1 } “*yes/no*” { send “yes\r”; exp_continue } “*password:*” { send “$pass\r” } } # 判断是否登录成功 expect { timeout { puts “登录后超时”; exit 1 } “*#*” { puts “登录成功!”; send “hostname\r” } “*$*” { puts “登录成功(普通用户)!”; send “whoami\r” } } expect eof

关键命令解析:

  • spawn:启动一个新的目标进程(如ssh, telnet)。
  • expect:等待进程输出,直到匹配到指定的模式字符串。支持通配符*和正则表达式。
  • send:向目标进程发送字符串。\r代表回车键(Enter),\n有时也可用,但\r更通用。
  • exp_continue:继续执行当前的expect块,而不是跳出。常用于处理像“yes/no”这样的中间提示。
  • set timeout:设置等待匹配的超时时间(秒)。-1为无限等待。
  • expect eof:等待spawn启动的进程结束。
  • interact:将控制权交还给用户,手动交互。常用于调试或脚本最后。

避坑技巧与注意事项:

  1. 模式匹配的精确性expect “password:”可能因为程序输出的大小写、空格(如Password:password:)而失败。使用更宽松的通配符,如expect “*password:*”是更稳健的做法。但也要避免匹配到无关输出。
  2. 超时设置:务必设置合理的timeout。网络延迟或系统负载可能导致响应慢。在关键步骤(如登录后)可以单独设置更长的超时或使用set timeout -1
  3. 错误处理:像示例2那样,对常见错误(超时、拒绝、密码错误)进行匹配和处理,能让你的脚本更健壮,而不是卡死。
  4. 密码管理绝对不要把密码硬编码在Expect脚本里。可以通过命令行参数、环境变量(在调用Expect脚本的Bash脚本中设置)、或者从加密文件中读取的方式传入。
    # 在Bash脚本中调用Expect脚本,通过环境变量传递(相对安全) export SSH_PASS=“myPass” ./auto_ssh.expect $host $user # 在Expect脚本中: set pass $env(SSH_PASS)
  5. 脚本调试:运行Expect脚本时加上-d参数(expect -d script.exp)可以开启调试模式,看到详细的匹配和发送过程,是排查问题的利器。

4. 安全实践与进阶方案探讨

无论采用哪种方式,密码安全都是重中之重。这里分享几个提升安全性的实践和更进阶的思路。

4.1 密码安全管理策略

  1. 最低原则——避免明文

    • 环境变量:在调用脚本的父Shell中设置环境变量,子进程(脚本)可以读取。使用后及时unset。例如export TEMP_PASS=“secret”; ./my_script.sh; unset TEMP_PASS
    • 受保护的文件:将密码存储在权限为600(仅所有者可读)的文件中,脚本运行时读取。确保该文件不在版本控制系统中。
    • 密钥认证(SSH场景的最佳实践):对于SSH,强烈推荐使用SSH密钥对代替密码登录。这是最安全、最标准的做法。脚本中直接使用ssh -i /path/to/private/key user@host,完全无需处理密码交互。
  2. 使用密码管理器或系统密钥环:在更复杂的企业环境中,可以考虑使用如VaultAnsible Vault或操作系统自带的密钥环(如gnome-keyringKeychain)来管理机密信息,脚本运行时通过API或命令行工具临时获取。

  3. Expect脚本的安全调用

    # wrapper.sh (Bash脚本) #!/bin/bash read -s -p “Enter SSH Password: ” SSH_PASS # 交互式输入,不回显 export SSH_PASS ./auto_ssh.expect “$1” “$2” unset SSH_PASS

4.2 混合使用与工具推荐

在实际项目中,我们常常需要混合使用这些技术。

  • Bash脚本内嵌Expect:对于只需要处理一两个交互点的复杂Bash脚本,可以使用expect -c来执行内联的Expect代码片段,避免创建单独的.exp文件。
    #!/bin/bash password=“secret” /usr/bin/expect -c “ set timeout 5 spawn sudo some-command expect \"*password*:\" send \"$password\r\" expect eof “
  • 使用sshpass工具:这是一个专门为SSH密码自动化设计的工具。它本质上是对管道和伪终端(pty)的一种封装,比写Expect脚本简单,但功能单一。
    # 安装: sudo apt-get install sshpass # 使用(密码作为参数,仍有安全风险): sshpass -p ‘your_password’ ssh user@host ‘ls -l’ # 使用(从文件读取密码): sshpass -f /path/to/password_file ssh user@host
    注意sshpass同样有密码泄露风险,且在某些严格的安全策略下可能被禁用。它只是提供了一个快捷方式,安全性上并未超越我们讨论的范畴。

4.3 针对特殊顽固命令的解决方案

有些命令,如标准的passwdsu,出于最高级别的安全考虑,会强制从终端设备(/dev/tty)读取密码,彻底关闭了标准输入通道。对于这类命令:

  1. 使用expect:这是最通用和可靠的解决方案,因为Expect可以模拟一个完整的伪终端(pty),欺骗这些命令。
  2. 使用chpasswdusermod(针对passwd:对于修改用户密码,可以使用非交互式的命令替代。
    # 使用chpasswd,密码通过标准输入提供 echo “username:newpassword” | sudo chpasswd # 使用usermod(某些系统支持) sudo usermod --password $(openssl passwd -6 ‘newpassword’) username
    这些替代命令是系统管理员的常用手段,但需要注意密码在命令行中出现的风险。

5. 常见问题排查与调试技巧实录

即使掌握了所有方法,在实际编写和运行脚本时,你仍然可能会遇到各种问题。下面是我在多年实践中总结的一些典型问题及其解决方法。

问题1:脚本执行时,密码输入似乎成功了,但后续命令报“权限错误”或直接跳过。

  • 可能原因1(管道/Here Document):目标命令没有从标准输入读取密码。例如,直接使用sudo而没有-S选项。解决方案:检查命令是否支持从stdin读取密码,并添加相应参数(如sudo -S,mysql -p实际上会提示,但可以用mysql --password=XXXMYSQL_PWD环境变量,但后者不安全)。
  • 可能原因2(管道):密码字符串末尾缺少换行符\n,命令在等待回车确认。解决方案:使用printf “password\n”替代echo
  • 可能原因3(Expect)expect模式匹配不准确,没有等到正确的提示符就发送了密码,导致密码被当作命令输入。解决方案:启用调试模式expect -d,观察程序的实际输出和脚本的匹配过程。放宽匹配模式,如使用“*password:*”

问题2:使用管道给ssh传递密码,完全无效。

  • 原因ssh命令默认强制从终端读取密码,这是其安全设计。解决方案:不要试图用管道或Here Document自动化ssh的密码输入。请使用ExpectSSH密钥认证

问题3:Expect脚本在匹配提示符时超时(timeout)。

  • 可能原因1:网络延迟或目标主机响应慢。解决方案:适当增加set timeout的值,比如从10改为30。
  • 可能原因2:提示符文本与expect语句中的模式不匹配。例如,提示是“Password: “而你写的是“password:”解决方案:使用更通用的通配符,并注意大小写和空格。expect “*assword:*”是一个很好的通用模式。
  • 可能原因3:目标程序的输出被缓冲,没有立即刷新到Expect。解决方案:在spawn后尝试加上stty -echoexpect命令后加exp_internal 1调试。对于某些程序,可能需要在其启动命令中添加强制刷新的参数,如ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no user@host

问题4:如何在脚本中判断密码是否输入正确?

  • 对于管道/Here Document:这很困难,因为错误信息可能和成功输出混在一起。通常通过检查命令的退出状态码$?来判断。非0状态码通常意味着失败(包括认证失败)。
    echo “pass” | sudo -S whoami 2>/dev/null if [ $? -eq 0 ]; then echo “sudo成功” else echo “sudo失败(可能是密码错误)” fi
  • 对于Expect:可以在发送密码后,期待一个表示成功的提示(如新的命令提示符$#),如果匹配到表示认证失败的文本(如“Permission denied”),则进行错误处理。参考前面示例2的健壮写法。

问题5:我的脚本在终端运行正常,但放到crontab里就不执行或报错。

  • 原因:cron环境与用户交互式Shell环境不同,缺少必要的环境变量(如PATH),并且没有关联的终端(tty)。解决方案
    1. 在脚本中显式设置关键环境变量,特别是PATH
    2. 对于需要终端的命令(包括某些使用sudo而不带-S的情况),在cron中可能失败。确保使用支持非交互式的方法(如sudo -S)。
    3. 将cron任务的所有输出(包括标准错误)重定向到日志文件,便于调试:* * * * * /path/to/script.sh >> /var/log/myjob.log 2>&1

调试工具箱:

  • set -x:在Bash脚本开头加上这行,会打印出脚本执行的每一行命令及其参数,是追踪脚本流程的神器。
  • expect -d:Expect的调试模式,显示详细的交互过程。
  • strace:对于非常顽固的命令,可以用strace -f -e trace=read,write,ioctl command来跟踪其所有的读写和IO控制调用,看它到底在从哪里读取数据。这是一个高级调试手段。

最后,我个人的一个强烈建议是:优先考虑“无需密码”的解决方案。比如,用SSH密钥代替密码,用sudoers文件配置NOPASSWD规则(在可控环境下)代替脚本输入sudo密码,用API Token代替密码调用接口。自动化脚本的安全边界,取决于其所需凭据的权限。尽量减少脚本所需凭据的权限和暴露面,是系统安全设计的根本原则。当自动化输入密码成为唯一选择时,再运用本文的方法,并务必谨记安全要点。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/29 11:50:16

5分钟彻底告别文件分享烦恼:秒传链接提取脚本完全指南

5分钟彻底告别文件分享烦恼&#xff1a;秒传链接提取脚本完全指南 【免费下载链接】rapid-upload-userscript-doc 秒传链接提取脚本 - 文档&教程 项目地址: https://gitcode.com/gh_mirrors/ra/rapid-upload-userscript-doc 你是否还在为百度网盘分享链接频繁失效而…

作者头像 李华
网站建设 2026/7/29 11:49:15

从Excel到企业数据资产池:文件数据入湖的治理规则不能省

导语 很多企业在搭建数据资产池的过程中&#xff0c;都会默认一个常识&#xff1a;把散落各处的Excel、CSV文件统统导入数据湖&#xff0c;自然就能沉淀出可用的数据资产。但实际落地过程中&#xff0c;我们观察到一个反常识结论&#xff1a;零散文件无规则直接入湖&#xff0c…

作者头像 李华
网站建设 2026/7/29 11:47:12

AI写专著全攻略:从构思到完稿,AI专著写作工具轻松搞定20万字!

写学术专著的挑战及AI辅助工具介绍 写学术专著是一件既锻炼学术能力&#xff0c;又考验心理耐力的事情。和团队合作写论文不一样&#xff0c;专著的写作往往是一个人独自完成的过程。从确定题目&#xff0c;到搭建内容框架&#xff0c;再到具体写作和不断修改&#xff0c;几乎…

作者头像 李华
网站建设 2026/7/29 11:42:33

达梦DEM(数据库监控管理平台)安装部署指南

软件准备 下载安装包DEM安装包&#xff1a; 下载链接&#xff1a;dem_setup_20260702_x86_linux_64_9.0.1.zip百度网盘地址&#xff1a;https://pan.baidu.com/s/1oqki-LBuW9NdOgXz-uDNSg?pwdea45提取码&#xff1a;ea45Tomcat安装包&#xff1a; 文件路径&#xff1a;dem_set…

作者头像 李华
网站建设 2026/7/29 11:40:53

Scroll Reverser:告别Mac滚动方向混乱,3分钟实现多设备智能切换

Scroll Reverser&#xff1a;告别Mac滚动方向混乱&#xff0c;3分钟实现多设备智能切换 【免费下载链接】Scroll-Reverser Per-device scrolling prefs on macOS. 项目地址: https://gitcode.com/gh_mirrors/sc/Scroll-Reverser 你是否曾经在MacBook的触控板和鼠标之间切…

作者头像 李华