最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Linux使用Expect腳本實(shí)現(xiàn)自動(dòng)化交互操作

 更新時(shí)間:2026年04月22日 09:23:15   作者:知遠(yuǎn)漫談  
Expect是一個(gè)基于Tcl的工具,用于自動(dòng)化處理需要輸入的交互式命令,它可以模擬人類與程序交互,實(shí)現(xiàn)無人值守的自動(dòng)化流程,文章介紹了Expect的基礎(chǔ)使用、常見場景,并提供了Java集成示例,同時(shí),文章還討論了Expect的局限性、替代方案及性能優(yōu)化建議,需要的朋友可以參考下

在 Linux 系統(tǒng)管理和運(yùn)維工作中,我們經(jīng)常需要與那些不支持非交互式輸入的命令打交道 —— 比如 ssh、scppasswd、ftptelnet 等。這些程序設(shè)計(jì)之初就假定用戶會(huì)在終端中手動(dòng)鍵入密碼或確認(rèn)信息,因此無法通過簡單的管道或重定向完成自動(dòng)化。

這時(shí)候,Expect 就登場了 !

Expect 是一個(gè)基于 Tcl(Tool Command Language)的擴(kuò)展工具,它能夠“模擬人類”,自動(dòng)響應(yīng)程序提出的交互式提示,從而實(shí)現(xiàn)完全無人值守的自動(dòng)化流程。無論你是系統(tǒng)管理員、DevOps 工程師,還是 Java 開發(fā)者希望集成自動(dòng)化部署,掌握 Expect 都是提升效率的關(guān)鍵技能之一。

什么是 Expect?

Expect 最初由 Don Libes 在 1990 年代開發(fā),目的是解決 Unix/Linux 下交互式程序難以腳本化的問題。它本質(zhì)上是一個(gè)“對(duì)話機(jī)器人”:你告訴它“當(dāng)看到某某提示時(shí),就輸入某某內(nèi)容”,它就會(huì)忠實(shí)地執(zhí)行下去。

它的核心思想是:

  • 啟動(dòng)目標(biāo)程序(如 ssh)
  • 監(jiān)聽程序輸出(如 “password:”)
  • 根據(jù)預(yù)設(shè)規(guī)則發(fā)送響應(yīng)(如 輸入密碼 + 回車)
  • 循環(huán)直到程序結(jié)束或超時(shí)

安裝 Expect

大多數(shù)現(xiàn)代 Linux 發(fā)行版默認(rèn)沒有安裝 Expect,但安裝非常簡單:

# Ubuntu / Debian
sudo apt update && sudo apt install expect -y

# CentOS / RHEL / Fedora
sudo yum install expect -y
# 或
sudo dnf install expect -y

# Arch Linux
sudo pacman -S expect

驗(yàn)證是否安裝成功:

expect -v

輸出類似:

expect version 5.45.4

Expect 基礎(chǔ)語法速覽

雖然 Expect 是 Tcl 的擴(kuò)展,但你不需要成為 Tcl 專家也能寫出實(shí)用腳本。以下是幾個(gè)關(guān)鍵命令:

命令說明
spawn啟動(dòng)一個(gè)新的進(jìn)程(你要自動(dòng)化的程序)
expect等待特定字符串出現(xiàn)(如 “password:”)
send發(fā)送字符串到子進(jìn)程(如密碼)
interact將控制權(quán)交還給用戶(用于調(diào)試)
set timeout N設(shè)置超時(shí)時(shí)間(秒),默認(rèn)10秒

第一個(gè) Expect 腳本:自動(dòng)登錄 SSH

假設(shè)我們要自動(dòng)登錄一臺(tái)遠(yuǎn)程服務(wù)器 192.168.1.100,用戶名為 admin,密碼為 secret123。

創(chuàng)建腳本文件 auto_ssh.exp

#!/usr/bin/expect -f

# 設(shè)置超時(shí)時(shí)間為20秒
set timeout 20

# 設(shè)置變量
set host "192.168.1.100"
set user "admin"
set password "secret123"

# 啟動(dòng) ssh 連接
spawn ssh $user@$host

# 等待密碼提示
expect {
    "*yes/no*" {
        send "yes\r"
        exp_continue
    }
    "*password:*" {
        send "$password\r"
    }
}

# 交出控制權(quán),進(jìn)入交互模式(可選)
interact

賦予執(zhí)行權(quán)限并運(yùn)行:

chmod +x auto_ssh.exp
./auto_ssh.exp

成功!你現(xiàn)在無需手動(dòng)輸入密碼即可登錄遠(yuǎn)程主機(jī)。

自動(dòng)修改用戶密碼

另一個(gè)常見場景:批量修改多臺(tái)服務(wù)器上的用戶密碼。

腳本 change_password.exp

#!/usr/bin/expect -f

set timeout 15
set user [lindex $argv 0]
set oldpass [lindex $argv 1]
set newpass [lindex $argv 2]

spawn passwd $user

expect "current password:"
send "$oldpass\r"

expect "new password:"
send "$newpass\r"

expect "retype new password:"
send "$newpass\r"

expect eof

調(diào)用方式:

./change_password.exp john old123 new456

注意:這里使用了 $argv 來接收命令行參數(shù),非常靈活。

批量執(zhí)行遠(yuǎn)程命令

有時(shí)我們不僅想登錄,還想在遠(yuǎn)程機(jī)器上執(zhí)行命令后退出。

腳本 remote_exec.exp

#!/usr/bin/expect -f

set timeout 30
set host [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set command [lindex $argv 3]

spawn ssh $user@$host $command

expect {
    "*yes/no*" {
        send "yes\r"
        exp_continue
    }
    "*password:*" {
        send "$password\r"
    }
}

expect eof

執(zhí)行示例:

./remote_exec.exp 192.168.1.100 admin secret123 "df -h"

這將在遠(yuǎn)程主機(jī)上執(zhí)行 df -h 并返回結(jié)果。

Expect 流程圖解(mermaid)

下面用 mermaid 圖表展示 Expect 腳本的典型工作流:

這個(gè)循環(huán)結(jié)構(gòu)是 Expect 的核心機(jī)制,理解它就能舉一反三寫出各種自動(dòng)化腳本。

Expect 在 Java 項(xiàng)目中的集成應(yīng)用

雖然 Expect 是 Tcl 腳本,但它完全可以被 Java 程序調(diào)用,實(shí)現(xiàn)“Java 控制交互式命令”的能力。

場景舉例:

  • 自動(dòng)化部署:Java 程序調(diào)用 Expect 腳本上傳 WAR 包到 Tomcat 服務(wù)器
  • 數(shù)據(jù)庫初始化:Java 調(diào)用 MySQL 客戶端并自動(dòng)輸入密碼執(zhí)行 SQL
  • 密鑰分發(fā):Java 程序批量調(diào)用 ssh-copy-id 并自動(dòng)應(yīng)答

Java 調(diào)用 Expect 腳本示例

我們先寫一個(gè)通用的 Java 工具類,用于執(zhí)行外部腳本并捕獲輸出:

import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class ExpectExecutor {
    /**
     * 執(zhí)行 Expect 腳本并返回輸出
     */
    public static String executeScript(String scriptPath, String... args) throws IOException, InterruptedException {
        List<String> command = new ArrayList<>();
        command.add("expect");
        command.add(scriptPath);
        for (String arg : args) {
            command.add(arg);
        }
        ProcessBuilder pb = new ProcessBuilder(command);
        pb.redirectErrorStream(true); // 合并錯(cuò)誤流和標(biāo)準(zhǔn)輸出
        Process process = pb.start();
        // 讀取輸出
        StringBuilder output = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
            }
        }
        int exitCode = process.waitFor();
        if (exitCode != 0) {
            throw new RuntimeException("Expect script failed with exit code: " + exitCode);
        }
        return output.toString();
    }
    public static void main(String[] args) {
        try {
            // 示例:執(zhí)行遠(yuǎn)程 df -h
            String result = executeScript(
                "/home/user/scripts/remote_exec.exp",
                "192.168.1.100", "admin", "secret123", "df -h"
            );
            System.out.println("遠(yuǎn)程磁盤使用情況:\n" + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

更復(fù)雜的 Java + Expect 場景:自動(dòng)化部署 WAR 包

假設(shè)你有一個(gè) Web 項(xiàng)目,編譯后生成 app.war,你想自動(dòng)部署到遠(yuǎn)程 Tomcat 的 webapps 目錄。

首先編寫 Expect 腳本 deploy_war.exp

#!/usr/bin/expect -f
set timeout 60
set host [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set local_war [lindex $argv 3]
set remote_dir [lindex $argv 4]
# 上傳文件
spawn scp $local_war $user@$host:$remote_dir/
expect {
    "*yes/no*" {
        send "yes\r"
        exp_continue
    }
    "*password:*" {
        send "$password\r"
    }
}
expect eof
# 登錄并重啟 Tomcat(假設(shè)路徑已知)
spawn ssh $user@$host
expect {
    "*yes/no*" {
        send "yes\r"
        exp_continue
    }
    "*password:*" {
        send "$password\r"
    }
}
expect "$ "
send "cd /opt/tomcat && ./bin/shutdown.sh\r"
expect "$ "
send "./bin/startup.sh\r"
expect "$ "
send "exit\r"
expect eof

然后在 Java 中調(diào)用:

public class WarDeployer {
    public static void deployWar(String host, String user, String password,
                                 String localWarPath, String remoteDir) {
        try {
            System.out.println("開始部署 WAR 包到 " + host + "...");
            String output = ExpectExecutor.executeScript(
                "/scripts/deploy_war.exp",
                host, user, password, localWarPath, remoteDir
            );
            System.out.println("部署完成!Tomcat 已重啟。\n輸出:\n" + output);
        } catch (Exception e) {
            System.err.println("部署失?。? + e.getMessage());
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        deployWar(
            "192.168.1.200",
            "deployer",
            "mypassword",
            "/target/myapp.war",
            "/opt/tomcat/webapps"
        );
    }
}

一鍵部署完成!再也不用手動(dòng) SCP + SSH + 重啟 Tomcat 了。

使用 Java 生成動(dòng)態(tài) Expect 腳本

有時(shí)你希望根據(jù)運(yùn)行時(shí)參數(shù)動(dòng)態(tài)生成 Expect 腳本,而不是預(yù)先寫死。這在 CI/CD 系統(tǒng)中特別有用。

下面是一個(gè) Java 類,負(fù)責(zé)生成臨時(shí) Expect 腳本并執(zhí)行:

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
public class DynamicExpectGenerator {
    public static String generateAndExecuteScript(String template, Object... params) {
        try {
            // 用參數(shù)填充模板
            String scriptContent = String.format(template, params);
            // 創(chuàng)建臨時(shí)腳本文件
            File tempScript = File.createTempFile("expect_", ".exp");
            tempScript.deleteOnExit(); // JVM退出時(shí)刪除
            Files.write(Paths.get(tempScript.getAbsolutePath()), scriptContent.getBytes());
            // 賦予執(zhí)行權(quán)限
            Process chmod = Runtime.getRuntime().exec("chmod +x " + tempScript.getAbsolutePath());
            chmod.waitFor();
            // 執(zhí)行腳本
            return ExpectExecutor.executeScript(tempScript.getAbsolutePath());
        } catch (Exception e) {
            throw new RuntimeException("生成或執(zhí)行腳本失敗", e);
        }
    }
    public static void main(String[] args) {
        String sshTemplate =
            "#!/usr/bin/expect -f\n" +
            "set timeout 20\n" +
            "spawn ssh %s@%s\n" +
            "expect {\n" +
            "    \"*yes/no*\" { send \"yes\\r\"; exp_continue }\n" +
            "    \"*password:*\" { send \"%s\\r\" }\n" +
            "}\n" +
            "expect \"$ \"\n" +
            "send \"%s\\r\"\n" +
            "expect \"$ \"\n" +
            "send \"exit\\r\"\n" +
            "expect eof";
        String result = generateAndExecuteScript(
            sshTemplate,
            "admin",           // 用戶名
            "192.168.1.100",   // 主機(jī)
            "secret123",       // 密碼
            "uptime"           // 要執(zhí)行的命令
        );
        System.out.println("遠(yuǎn)程 uptime 結(jié)果:\n" + result);
    }
}

這種“模板+參數(shù)”的方式讓 Expect 腳本具備了極強(qiáng)的靈活性,適合集成進(jìn)配置管理系統(tǒng)或 DevOps 平臺(tái)。

Expect 調(diào)試技巧

Expect 腳本調(diào)試有時(shí)比較棘手,因?yàn)榭床坏絻?nèi)部狀態(tài)。以下是一些實(shí)用技巧:

1. 開啟日志輸出

在腳本開頭加入:

log_user 1
exp_internal 1

這會(huì)輸出詳細(xì)的匹配過程。

2. 使用interact臨時(shí)接管

在關(guān)鍵步驟后插入 interact,你可以手動(dòng)接管終端,觀察當(dāng)前狀態(tài):

expect "password:"
send "$password\r"
interact  ;# 此時(shí)你可以手動(dòng)輸入命令調(diào)試

3. 捕獲超時(shí)錯(cuò)誤

expect {
    timeout {
        puts "操作超時(shí),請(qǐng)檢查網(wǎng)絡(luò)或密碼是否正確"
        exit 1
    }
    "*password:*" {
        send "$password\r"
    }
}

Expect 的局限性與替代方案

雖然 Expect 強(qiáng)大,但也存在一些限制:

問題說明
? 依賴終端輸出格式如果程序輸出變化(比如語言、提示符改變),腳本可能失效
? 不適合高并發(fā)每個(gè) spawn 啟動(dòng)獨(dú)立進(jìn)程,大量并發(fā)時(shí)資源消耗大
? 安全性風(fēng)險(xiǎn)密碼明文寫在腳本中,容易泄露

替代方案推薦:

SSH 密鑰認(rèn)證 —— 免密碼登錄的最佳實(shí)踐
教程參考:https://www.ssh.com/academy/ssh/keygen

Ansible —— 企業(yè)級(jí)自動(dòng)化工具,支持無密碼操作
官網(wǎng):https://www.ansible.com/

Fabric (Python) —— 簡潔的遠(yuǎn)程執(zhí)行庫
文檔:http://www.fabfile.org/

JSch (Java SSH 庫) —— 純 Java 實(shí)現(xiàn) SSH,無需 Expect

Java 替代方案:使用 JSch 庫實(shí)現(xiàn) SSH 自動(dòng)化

如果你希望完全脫離 Expect,在 Java 內(nèi)部實(shí)現(xiàn) SSH 自動(dòng)化,推薦使用 JSch

添加 Maven 依賴:

<dependency>
    <groupId>com.jcraft</groupId>
    <artifactId>jsch</artifactId>
    <version>0.1.55</version>
</dependency>

Java 示例代碼:

import com.jcraft.jsch.*;
public class JschExample {
    public static void executeRemoteCommand(String host, String user, String password, String command) {
        try {
            JSch jsch = new JSch();
            Session session = jsch.getSession(user, host, 22);
            session.setPassword(password);
            // 跳過主機(jī)密鑰檢查(僅用于測試環(huán)境)
            session.setConfig("StrictHostKeyChecking", "no");
            session.connect();
            Channel channel = session.openChannel("exec");
            ((ChannelExec) channel).setCommand(command);
            channel.setInputStream(null);
            ((ChannelExec) channel).setErrStream(System.err);
            InputStream in = channel.getInputStream();
            channel.connect();
            byte[] tmp = new byte[1024];
            while (true) {
                while (in.available() > 0) {
                    int i = in.read(tmp, 0, 1024);
                    if (i < 0) break;
                    System.out.print(new String(tmp, 0, i));
                }
                if (channel.isClosed()) {
                    if (in.available() > 0) continue;
                    break;
                }
                Thread.sleep(1000);
            }
            channel.disconnect();
            session.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        executeRemoteCommand("192.168.1.100", "admin", "secret123", "ls -la /tmp");
    }
}

優(yōu)勢(shì):

  • 純 Java,無需外部腳本
  • 支持 SFTP 文件傳輸
  • 可集成密鑰認(rèn)證
  • 更安全(密碼可從配置中心動(dòng)態(tài)獲?。?/li>

Expect 高級(jí)技巧:正則匹配與多分支處理

Expect 支持使用 -re 參數(shù)進(jìn)行正則表達(dá)式匹配,應(yīng)對(duì)更復(fù)雜的輸出場景。

例如,等待多種可能的提示:

expect {
    -re "(yes/no)|(continue connecting)" {
        send "yes\r"
        exp_continue
    }
    "*password:*" {
        send "$password\r"
    }
    "*denied*" {
        puts "認(rèn)證失敗!"
        exit 1
    }
    timeout {
        puts "連接超時(shí)"
        exit 1
    }
}

你也可以捕獲匹配的內(nèi)容:

expect -re {Welcome, (.*)!}
set username $expect_out(1,string)
puts "登錄用戶:$username"

實(shí)用 Expect 腳本合集

1. 自動(dòng)備份遠(yuǎn)程 MySQL 數(shù)據(jù)庫

#!/usr/bin/expect -f

set host [lindex $argv 0]
set dbuser [lindex $argv 1]
set dbpass [lindex $argv 2]
set dbname [lindex $argv 3]
set backup_file "/backups/${dbname}_$(date +%Y%m%d).sql"

spawn ssh admin@$host "mysqldump -u$dbuser -p$dbpass $dbname > $backup_file"

expect {
    "*password:*" { send "ssh_password\r" }
    timeout { puts "SSH 超時(shí)"; exit 1 }
}

expect eof
puts "備份完成:$backup_file"

2. 批量測試多臺(tái)主機(jī)連通性

#!/usr/bin/expect -f

set timeout 5
set hosts {"192.168.1.100" "192.168.1.101" "192.168.1.102"}
set user "monitor"
set password "monitor123"

foreach host $hosts {
    puts "正在測試 $host..."

    spawn ssh $user@$host "echo 'OK'"

    expect {
        "*password:*" {
            send "$password\r"
            expect {
                "OK" { puts "$host ? 正常" }
                timeout { puts "$host ? 無響應(yīng)" }
            }
        }
        timeout { puts "$host ? SSH 超時(shí)" }
    }

    expect eof
}

安全加固建議

雖然方便,但 Expect 腳本中的明文密碼是安全隱患。以下是幾種加固方法:

方法 1:從環(huán)境變量讀取密碼

set password $env(MY_SECRET_PASSWORD)

啟動(dòng)前設(shè)置:

export MY_SECRET_PASSWORD="real_password"
./script.exp

方法 2:從加密文件讀取

結(jié)合 gpgopenssl 解密:

set password [exec echo mypass.enc | gpg --decrypt --quiet --batch --yes --passphrase-file key.txt]

方法 3:使用 Vault 或 Secret Manager

在 Java 中集成 HashiCorp Vault 獲取密碼,再傳給 Expect 腳本。

Expect 性能優(yōu)化建議

當(dāng)需要并發(fā)執(zhí)行多個(gè) Expect 腳本時(shí),注意以下幾點(diǎn):

  1. 避免阻塞主線程 —— 使用線程池異步執(zhí)行
  2. 限制并發(fā)數(shù) —— 避免同時(shí)打開過多 SSH 連接
  3. 設(shè)置合理超時(shí) —— 避免僵尸進(jìn)程
  4. 復(fù)用連接 —— 如可能,一次 SSH 執(zhí)行多個(gè)命令

Java 并發(fā)執(zhí)行示例:

import java.util.concurrent.*;
public class ConcurrentExpectRunner {
    private static final ExecutorService executor = Executors.newFixedThreadPool(5);
    public static Future<String> runAsync(String script, String... args) {
        return executor.submit(() -> ExpectExecutor.executeScript(script, args));
    }
    public static void main(String[] args) throws Exception {
        List<Future<String>> futures = new ArrayList<>();
        // 并發(fā)執(zhí)行10臺(tái)主機(jī)的磁盤檢查
        for (int i = 100; i <= 110; i++) {
            String host = "192.168.1." + i;
            futures.add(runAsync("/scripts/check_disk.exp", host, "admin", "secret123"));
        }
        // 收集結(jié)果
        for (Future<String> future : futures) {
            System.out.println(future.get()); // 阻塞直到完成
        }
        executor.shutdown();
    }
}

Expect 與 Cron 結(jié)合實(shí)現(xiàn)定時(shí)任務(wù)

將 Expect 腳本加入 crontab,實(shí)現(xiàn)無人值守的周期性操作。

編輯定時(shí)任務(wù):

crontab -e

添加一行(每天凌晨2點(diǎn)執(zhí)行備份):

0 2 * * * /home/user/scripts/mysql_backup.exp 192.168.1.100 dbuser dbpass mydb >> /var/log/backup.log 2>&1

記得設(shè)置腳本權(quán)限和日志輪轉(zhuǎn)!

Expect 在 DevOps 中的角色

在 CI/CD 流水線中,Expect 常用于:

  • 自動(dòng)化部署遺留系統(tǒng)(不支持 API 的設(shè)備)
  • 初始化網(wǎng)絡(luò)設(shè)備配置(路由器、交換機(jī))
  • 數(shù)據(jù)庫遷移腳本執(zhí)行
  • 容器外服務(wù)的健康檢查

雖然現(xiàn)代工具如 Ansible、Terraform 更受歡迎,但在“最后一公里”的特殊場景中,Expect 仍是不可替代的利器。

總結(jié):何時(shí)該用 Expect?

場景推薦方案
簡單一次性 交互? Expect 腳本
企業(yè)級(jí)自動(dòng)化?? 優(yōu)先考慮 Ansible / SaltStack
Java 應(yīng)用內(nèi)集成? JSch / SSHJ 庫
高安全性要求? 避免明文密碼,改用密鑰或 Vault
多平臺(tái)兼容? Expect 僅限 Unix/Linux,Windows 需 Cygwin

未來展望:Expect 的演進(jìn)方向

隨著基礎(chǔ)設(shè)施即代碼(IaC)和 API 化趨勢(shì),Expect 的使用確實(shí)在減少。但它的思想 —— “程序模擬人類交互” —— 依然活躍在:

  • UI 自動(dòng)化測試(Selenium、Playwright)
  • 聊天機(jī)器人(自然語言交互)
  • RPA(機(jī)器人流程自動(dòng)化)

學(xué)習(xí) Expect,不僅是學(xué)一個(gè)工具,更是理解“自動(dòng)化”的本質(zhì)。

結(jié)語

Expect 是 Linux 世界中一顆低調(diào)但璀璨的明珠。它不華麗,不時(shí)髦,卻能在關(guān)鍵時(shí)刻解決別人束手無策的問題。無論是系統(tǒng)管理員、運(yùn)維工程師,還是 Java 開發(fā)者,掌握 Expect 都能讓你在自動(dòng)化之路上走得更遠(yuǎn)、更穩(wěn)。

以上就是Linux使用Expect腳本實(shí)現(xiàn)自動(dòng)化交互操作的詳細(xì)內(nèi)容,更多關(guān)于Linux Expect自動(dòng)化交互操作的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論

新化县| 汶上县| 新竹市| 澄城县| 闽清县| 乌拉特前旗| 郁南县| 杭锦后旗| 延吉市| 鲜城| 万盛区| 广水市| 县级市| 道孚县| 大连市| 曲靖市| 建阳市| 安陆市| 中卫市| 莲花县| 定结县| 临江市| 马山县| 从江县| 凤庆县| 吐鲁番市| 光山县| 平塘县| 轮台县| 安新县| 怀宁县| 凤台县| 东乡县| 丰顺县| 甘孜县| 深圳市| 中方县| 新郑市| 集贤县| 南涧| 临洮县|