【香橙派系列教程】(十六) 语音模块与阿里云结合
【十六】语音模块与阿里云结合
本小节实现功能使用语音模块和摄像头在香橙派上做垃圾智能分类识别。
大致流程:说出“识别垃圾类型”口令,语音模块接收到之后通过串口发送字符串指令,随后进行拍照(请确保香橙派已经驱动起来了摄像头,我们需要将此步骤设置为开机自启动)将照片发送到阿里云进行识别,返回字符串做出判断,由香橙派再次发送字符串给语音模块,反馈出得到的垃圾类型。
文章目录
- 【十六】语音模块与阿里云结合
- 1.环境准备
- 2.代码实现
- uartTool.h
- uartTool.c
- garbage.py
- garbage.c
- garbage.h
- main.c
- 3.编译运行
- 附录:
- 1.如何打开串口5
- 2.popen函数
- 3.fgets函数
- 4.remove函数
- 5.assess函数
- 6.awk
- 7.写个shell脚本用于杀死运行的进程
1.环境准备
- 将语音模块接在UART5的位置
- 在orange pi 3.0.6上确认已经配置开启了uart5:(overlays=uart5)
串口5默认是关闭的,如果没有打开请看附录部分
orangepi@orangepizero2:~/garbage$ cat /boot/orangepiEnv.txt
verbosity=1
bootlogo=false
console=both
disp_mode=1920x1080p60
overlay_prefix=sun50i-h616
rootdev=UUID=15a0010c-94e1-412f-b030-199e90c16cb1
rootfstype=ext4
overlays=uart5 i2c3
usbstoragequirks=0x2537:0x1066:u,0x2537:0x1068:u
- 同时将USB摄像头接到香橙派上
- 确认已经运行了mjpg-streamer服务
orangepi@orangepizero2:~/garbage$ ps ax | grep mjpg
1704 ? S 0:00 /bin/bash /home/orangepi/mjpg.sh
1710 ? Sl 0:29 ./mjpg_streamer -i ./input_uvc.so -d /dev/video1 -u -f 30 -o ./output_http.so -w
./www
5594 pts/0 S+ 0:00 grep --color=auto mjpg
orangepi@orangepizero2:~/garbage$
2.代码实现
-
首先创建garbage目录,将garbage.c、garbage.h、 garbage.py三个文件拷贝进来。
-
参照《基于官方外设开发,Linux原生串口开发》代码实现, 修改uartTool.h:
uartTool.h
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "wiringSerial.h"
#ifndef __UARTTOOL__H
#define __UARTTOOL__H
int my_serialOpen (const char *device, const int baud) ;
void my_serialSendstring (const int fd, const unsigned char *s, int len) ;
int my_serialGetstring (const int fd, unsigned char *buffer) ;
#define SERIAL_DEV "/dev/ttyS5"
#define BAUD 115200
#endif
uartTool.c
#include "wiringSerial.h"
#include "uartTool.h"
int my_serialOpen (const char *device, const int baud)
{
struct termios options ; // 创建一个termios结构体,用于串口参数设置
speed_t myBaud ; // 创建一个速度类型的变量 myBaud,用于保存波特率
int status, fd ; // 创建整数类型的变量 status 和 fd,用于保存状态和文件描述符
switch (baud){ // 根据传入的波特率参数选择合适的波特率常数
case 9600: myBaud = B9600 ; break ;
case 115200: myBaud = B115200 ; break ;
}
if ((fd = open (device, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK)) == -1) // 打开串口设备,设置打开选项
return -1 ; // 如果打开失败,返回错误代码 -1
fcntl (fd, F_SETFL, O_RDWR) ; // 设置文件状态标志
// Get and modify current options: 获取并修改当前的串口参数:
tcgetattr (fd, &options) ; // 获取当前的串口参数
cfmakeraw (&options) ; // 初始化 termios 结构体为原始模式
cfsetispeed (&options, myBaud) ; // 设置输入波特率
cfsetospeed (&options, myBaud) ; // 设置输出波特率
options.c_cflag |= (CLOCAL | CREAD) ; // 本地连接和使能接收
options.c_cflag &= ~PARENB ; // 禁用奇偶校验
options.c_cflag &= ~CSTOPB ; // 1位停止位
options.c_cflag &= ~CSIZE ; // 用数据位掩码清空数据位设置
options.c_cflag |= CS8 ; // 设置8位数据位
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG) ; // 禁用规范输入
options.c_oflag &= ~OPOST ; // 禁用输出处理
options.c_cc [VMIN] = 0 ; // 读取数据的最小字符数
options.c_cc [VTIME] = 100 ; // Ten seconds (100 deciseconds) 超时等待时间(十分之一秒100ms)
tcsetattr (fd, TCSANOW, &options) ; // 设置新的串口参数
ioctl (fd, TIOCMGET, &status); // 获取串口控制模式状态
status |= TIOCM_DTR ; // 设置 DTR(数据终端就绪)位
status |= TIOCM_RTS ; // 设置 RTS(请求发送)位
ioctl (fd, TIOCMSET, &status); // 设置串口控制模式状态
usleep (10000) ; // 暂停 10 毫秒
return fd ; // 返回串口文件描述符
}
//会传递0XAA 0X55 0X46 0x00 0X55 0XAA类似的参数
void my_serialSendstring (const int fd, const unsigned char *s, int len)
{
int ret ;
ret = write (fd,s,len);
if (ret < 0)
printf ("Serial Sendstring Error\n") ;
}
int my_serialGetstring (const int fd, unsigned char *buffer)
{
int n_read ;
n_read = read (fd, buffer, 32) ;
return n_read ;
}
garbage.py
# -*- coding: utf-8 -*-
# 引入依赖包
# pip install alibabacloud_imagerecog20190930
import os
import io
from urllib.request import urlopen
from alibabacloud_imagerecog20190930.client import Client
from alibabacloud_imagerecog20190930.models import ClassifyingRubbishAdvanceRequest
from alibabacloud_tea_openapi.models import Config
from alibabacloud_tea_util.models import RuntimeOptions
config = Config(
# 创建AccessKey ID和AccessKey Secret,请参考https://help.aliyun.com/document_detail/175144.html。
# 如果您用的是RAM用户的AccessKey,还需要为RAM用户授予权限AliyunVIAPIFullAccess,请参考https://help.aliyun.com/document_detail/145025.html
# 从环境变量读取配置的AccessKey ID和AccessKey Secret。运行代码示例前必须先配置环境变量。
access_key_id=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID'),
access_key_secret=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),
# 访问的域名
endpoint='imagerecog.cn-shanghai.aliyuncs.com',
# 访问的域名对应的region
region_id='cn-shanghai'
)
#封装成函数
def alibaba_garbage():
#场景一:文件在本地
img = open(r'/home/orangepi/trash/test.jpg', 'rb')#以后把拍到的垃圾文件统一放到这个文件里面,不用跟着改了
#场景二:使用任意可访问的url
# url = 'https://viapi-test-bj.oss-cn-beijing.aliyuncs.com/viapi-3.0domepic/imagerecog/ClassifyingRubbish/ClassifyingRubbish1.jpg'
#img = io.BytesIO(urlopen(url).read())
classifying_rubbish_request = ClassifyingRubbishAdvanceRequest()
classifying_rubbish_request.image_urlobject = img
runtime = RuntimeOptions()
try:
# 初始化Client
client = Client(config)
response = client.classifying_rubbish_advance(classifying_rubbish_request, runtime)
# 获取整体结果
print(response.body.to_map()['Data']['Elements'][0]['Category'])
return response.body.to_map()['Data']['Elements'][0]['Category']
except Exception as error:
return '获取失败'
if __name__ == "__main__":
alibaba_garbage()
garbage.c
第一种写法:
#include <Python.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void garbage_init(){
Py_Initialize();
// 将当前路径添加到sys.path中
PyObject *sys = PyImport_ImportModule("sys");
PyObject *path = PyObject_GetAttrString(sys, "path");
PyList_Append(path, PyUnicode_FromString("."));
}
void garbage_final()
{
// 关闭Python解释器
Py_Finalize();
}
char *garbage_category(char *category)
{
// 导入para模块
PyObject *pModule = PyImport_ImportModule("garbage");
if (!pModule)
{
PyErr_Print();
printf("Error: failed to load garbage.py\n");
goto FAILED_MODULE; //goto的意思就是如果运行到这里就直接跳转到FAILED_MODULE
}
//获取say_funny函数对象
PyObject *pFunc = PyObject_GetAttrString(pModule, "alibaba_garbage");
if (!pFunc)
{
PyErr_Print();
printf("Error: failed to load say_funny\n");
goto FAILED_FUNC;
}
//创建一个字符串作为参数
//char *category = "comedy";
//PyObject *pArgs = Py_BuildValue("(s)", category);//字符串加括号表示他是一个包含字符串的元组
//调用say_funny函数并获取返回值
PyObject *pValue = PyObject_CallObject(pFunc, NULL);
if (!pValue)
{
PyErr_Print();
printf("Error: function call failed\n");
goto FAILED_VALUE;
}
//将返回值转换为C类型
char *result = NULL;
if (!PyArg_Parse(pValue, "s", &result))
{
PyErr_Print();
printf("Error: parse failed\n");
goto FAILED_RESULT;
}
//打印返回值
//printf("pValue=%s\n", result);
// 为垃圾分类信息分配内存,复制返回值
category = (char *)malloc(sizeof(char) * (strlen(result) + 1));
memset(category, 0, (strlen(result) + 1));
strncpy(category, result, (strlen(result) + 1));
//释放所有引用的Python对象
FAILED_RESULT:
Py_DECREF(pValue);
FAILED_VALUE:
Py_DECREF(pFunc);
FAILED_FUNC:
Py_DECREF(pModule);
FAILED_MODULE:
return category;
}
garbage.c
第二种写法:
#include <stdio.h>
#include <Python.h>
#include "garbage.h"
void garbage_init()
{
Py_Initialize();// 初始化Python解释器
PyObject* sysPath = PySys_GetObject("path");// 获取sys.path对象
// 将当前路径添加到sys.path中
PyList_Append(sysPath, PyUnicode_DecodeFSDefault(".")); // PyUnicode_FromString将c字符串转换成Python字符串
}
void garbage_final()
{
// 关闭Python解释器
Py_Finalize();
}
char *garbage_category(char *category)
{
// 导入Python模块
PyObject* pModule = PyImport_ImportModule("garbage");
if (pModule != NULL) {
// 获取Python函数对象
PyObject* pFunction = PyObject_GetAttrString(pModule, "alibaba_garbage");
if (pFunction != NULL && PyCallable_Check(pFunction)) {
// 调用Python函数,这里是无参数调用
PyObject* pArgs = PyTuple_New(0); // 传递空参数元组
PyObject* pResult = PyObject_CallObject(pFunction, pArgs);
if (pResult != NULL) {
// 将返回值转换为C类型
char *result = NULL;
if (!PyArg_Parse(pResult, "s", &result)) {
PyErr_Print();
printf("Error: parse failed\n");
}
// 打印返回值
printf("pResult = %s\n", result);
// 为垃圾分类信息分配内存,复制返回值
category = (char *)malloc(sizeof(char) * (strlen(result) + 1));
memset(category, 0, (strlen(result) + 1));
strncpy(category, result, (strlen(result) + 1));
Py_DECREF(pResult);
} else {
PyErr_Print(); // 打印Python错误信息
}
Py_DECREF(pFunction);
Py_DECREF(pArgs);
} else {
PyErr_Print();
}
Py_DECREF(pModule);
} else {
PyErr_Print();
}
return category;
}
garbage.h
#ifndef __GARBAGE__H
#define __GARBAGE__H
void garbage_init();
void garbage_final();
char *garbage_category(char *category);
// 增加拍照指令和照片路径宏定义
#define WGET_CMD "wget http://127.0.0.1:8080/?action=snapshot -O /tmp/garbage.jpg"
#define GARBAGE_FILE "/tmp/garbage.jpg"
#endif
main.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include "uartTool.h"
#include "garbage.h"
// 判断进程是否在运行
static int detect_process(const char * process_name)
{
int n = -1;
FILE *strm;
char buf[128] = {0};
// 构造命令字符串,通过ps命令查找进程
sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);
// 使用popen执行命令并读取输出
if ((strm = popen(buf, "r")) != NULL) {
if (fgets(buf, sizeof(buf), strm) != NULL) {
n = atoi(buf); // 将进程ID字符串转换为整数
}
}
else {
return -1; // popen失败
}
pclose(strm); // 关闭popen打开的文件流
return n;
}
int main(int argc, char *argv[])
{
int serial_fd = -1;
int ret = -1;
unsigned char buffer[6] = {0xAA, 0X55, 0x00, 0x00, 0x55, 0xAA};
int len = 0;
char *category = NULL;
// 初始化串口和垃圾分类模块
garbage_init ();
// 用于判断mjpg_streamer服务是否已经启动
ret = detect_process ("mjpg_streamer");
if (-1 == ret) {
printf("detect process failed\n");
goto END;
}
// 打开串口
serial_fd = my_serialOpen (SERIAL_DEV, BAUD);
if (-1 == serial_fd) {
goto END;
}
while (1) {
// 从串口读取数据
len = my_serialGetstring (serial_fd, buffer);
printf("lend = %d, buf[2] = 0x%x\n", len, buffer[2]);
if (len > 0 && buffer[2] == 0x46) {
buffer[2] = 0x00;
// 在执行wget命令之前添加调试输出
printf("Executing wget command...\n");
// 使用系统命令拍照
system(WGET_CMD);
// 在执行wget命令之后添加调试输出
printf("Wget command executed.\n");
// 判断垃圾种类
if (0 == access(GARBAGE_FILE, F_OK)) {
category = garbage_category (category);
if (strstr(category, "干垃圾")) {
buffer[2] = 0x41;
}
else if (strstr(category, "湿垃圾")) {
buffer[2] = 0x42;
}
else if (strstr(category, "可回收垃圾")) {
buffer[2] = 0x43;
}
else if (strstr(category, "有害垃圾")) {
buffer[2] = 0x44;
}
else {
buffer[2] = 0x45; //未识别到垃圾类型
}
}
else {
buffer[2] = 0x45; //识别失败
}
// 发送分类结果到串口
my_serialSendstring (serial_fd, buffer, 6);
buffer[2] = 0x00;
remove(GARBAGE_FILE); // 删除拍照文件
}
}
END:
close(serial_fd);
// 释放垃圾分类资源
garbage_final();
return 0;
}
3.编译运行
编译
gcc -o test *.c *.h -I /usr/include/python3.10 -l python3.10
执行
sudo -E ./test
sudo -E 命令用于在以超级用户权限运行命令的同时,保留环境变量
查看进程
ps -ax | grep mjpg_streamer | grep -v grep
ps -ax | grep ./test | grep -v grep
ps aux | grep './test' | grep -v grep | awk '{print $2}'
杀死进程
kill -9 pid (-9——-SIGKILL)
附录:
1.如何打开串口5
- 由 26pin 接口的原理图可知,Orange Pi Zero 2 可用的 uart 为 uart5
如果使用的为 Linux5.16 内核的系统,uart5 默认是关闭的,需要手动打开才能
使用。
在/boot/orangepiEnv.txt 中加入下面红色字体部分的配置,然后重启 Linux 系统就
可以打开 uart5。
orangepi@orangepi:~$ sudo vim /boot/orangepiEnv.txt
overlays=uart5
- 进入 linux 系统后,先确认下/dev 下是否存在 uart5 的设备节点
root@orangepi:~# ls /dev/ttyS5
/dev/ttyS5
- 然后开始测试 uart5 接口,先使用杜邦线短接要测试的 uart5 接口的 rx 和 tx
- 使用 wiringOP 中的 gpio 命令测试串口的回环功能如下所示,如果能看到下面的打印,说明串口通信正常
orangepi@orangepi:~$ gpio serial /dev/ttyS5
Out: 0: -> 0
Out: 1: -> 1
Out: 2: -> 2
Out: 3: -> 3^C
2.popen函数
popen
是一个在 C 语言中用于打开进程管道的函数,它允许你创建一个进程,并与该进程进行通信。这个函数通常用于执行一个命令,并从其标准输出读取数据,或者向其标准输入写入数据。
函数原型如下:
FILE *popen(const char *command, const char *type);
command
:要执行的命令字符串。type
:指定了打开管道的方式,可以是以下两种模式之一:"r"
:读取模式,从子进程的标准输出读取数据。"w"
:写入模式,向子进程的标准输入写入数据。
如果函数调用成功,它会返回一个指向新打开的进程文件的指针,你可以使用标准的文件I/O函数(如 fread
、fwrite
、fclose
等)来操作这个文件。如果调用失败,它会返回 NULL
。
使用 popen
时需要注意几点:
- 必须使用
pclose
函数来关闭通过popen
打开的管道,以确保子进程正确终止。 - 管道的读写操作是阻塞的,即如果管道的缓冲区满了,写操作会等待直到有空间可用;如果缓冲区空了,读操作会等待直到有数据可读。
- 使用
popen
时要小心处理错误和异常情况,确保资源被正确释放。
下面是一个简单的使用 popen
的例子:
#include <stdio.h>
int main() {
FILE *fp;
char buffer[128];
// 打开一个管道来读取 ls 命令的输出
fp = popen("ls", "r");
if (fp == NULL) {
perror("popen");
return 1;
}
// 读取输出
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer);
}
// 关闭管道
pclose(fp);
return 0;
}
这个例子中,我们使用 popen
来执行 ls
命令,并读取其输出,然后逐行打印出来。最后,使用 pclose
来关闭管道。
3.fgets函数
fgets
是C语言标准库中的一个函数,用于从文件或输入流中读取字符串。这个函数会从指定的输入流中读取字符,直到遇到换行符 \n
或达到指定的字符数为止,并将读取的字符串存储在提供的缓冲区中。
函数原型如下:
char *fgets(char *str, int num, FILE *stream);
str
:指向字符数组的指针,用于存储读取的字符串。num
:指定最多要读取的字符数,包括空字符(\0
)。stream
:指向FILE
结构的指针,表示要从哪个文件或输入流读取数据。
如果 fgets
成功读取数据,它会返回 str
指向的字符串。如果发生错误或提前遇到文件结束(EOF),它会返回 NULL
。
以下是 fgets
函数的一些关键点:
fgets
会将读取到的第一个换行符包含在返回的字符串中,但在字符串的末尾会添加一个空字符(\0
)。- 如果读取的字符数达到
num - 1
之前没有遇到换行符,fgets
将停止读取,并将空字符添加到字符串末尾。 - 如果
num
大于1,str
必须有足够的空间来存储至少num - 1
个字符加上空字符。
下面是一个使用 fgets
的示例代码:
#include <stdio.h>
int main() {
char buffer[100];
FILE *file = fopen("example.txt", "r"); // 打开文件以供读取
if (file == NULL) {
perror("Error opening file");
return 1;
}
// 从文件中读取一行
if (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("Read line: %s", buffer);
}
// 关闭文件
fclose(file);
return 0;
}
在这个例子中,我们使用 fgets
从文件 example.txt
中读取一行文本,并将读取的内容存储在 buffer
数组中。然后,我们打印这行文本。最后,我们使用 fclose
函数关闭文件。如果文件打开失败,我们使用 perror
打印错误信息。
4.remove函数
在C语言中,remove
函数用于删除文件系统中的一个文件。这个函数定义在 <stdio.h>
头文件中,其原型如下:
int remove(const char *filename);
filename
:要删除的文件的路径名。
函数返回值:
- 如果成功删除文件,返回0。
- 如果失败,返回-1,并设置
errno
以指示错误类型。
使用示例
#include <stdio.h>
int main() {
const char *filename = "example.txt";
if (remove(filename) == 0) {
printf("File '%s' has been successfully deleted.\n", filename);
} else {
perror("Failed to delete file");
}
return 0;
}
在这个示例中,我们尝试删除名为 example.txt
的文件。如果删除成功,会打印一条成功消息;如果失败,则使用 perror
函数打印错误信息。
注意事项
- 权限问题:确保程序有足够的权限来删除指定的文件。
- 文件状态:如果文件正在被使用(例如,被其他程序打开),可能无法删除。
- 目录项限制:某些文件系统可能有删除文件的限制,例如文件名长度或字符限制。
- 错误处理:始终检查
remove
函数的返回值,并适当处理错误。
remove
函数是处理文件删除的简单方法,但应谨慎使用,特别是在生产环境中,以避免意外删除重要文件。
5.assess函数
在C语言中,access
函数用于检查进程是否可以对文件进行特定的访问。这个函数定义在 <unistd.h>
头文件中(在Windows上是 io.h
),其原型如下:
int access(const char *pathname, int mode);
pathname
:要检查访问权限的文件或目录的路径。mode
:指定要检查的访问权限类型,可以是以下宏的组合:F_OK
:文件是否存在。R_OK
:文件是否可读。W_OK
:文件是否可写。X_OK
:文件是否可执行(在UNIX系统中,这个标志检查文件是否具有执行权限;在Windows系统中,这个标志被忽略)。
函数返回值:
- 如果指定的文件存在并且具有相应的访问权限,返回0。
- 如果文件不存在或没有相应的访问权限,返回-1,并设置
errno
以指示错误类型。
使用示例
#include <stdio.h>
#include <unistd.h>
int main() {
const char *filename = "example.txt";
// 检查文件是否存在
if (access(filename, F_OK) != -1) {
// 检查文件是否可读
if (access(filename, R_OK) != -1) {
printf("File '%s' exists and is readable.\n", filename);
} else {
perror("File is not readable");
}
} else {
perror("File does not exist");
}
return 0;
}
在这个示例中,我们首先检查 example.txt
文件是否存在,然后检查它是否可读。如果文件存在但不可读,我们使用 perror
函数打印错误信息。
注意事项
- 权限问题:
access
函数检查的是文件的访问权限,这可能受到操作系统安全策略和用户权限的影响。 - 符号链接:在UNIX系统中,如果
pathname
是一个符号链接,access
函数的行为取决于编译时定义的宏(如AT_SYMLINK_NOFOLLOW
)。 - 文件状态:即使文件当前具有访问权限,文件的状态也可能随时改变,例如被其他用户删除或修改权限。
- 错误处理:始终检查
access
函数的返回值,并适当处理错误。
access
函数是检查文件访问权限的简单方法,但应谨慎使用,特别是在需要高度安全性的应用程序中。
6.awk
awk
是一种强大的文本处理工具,广泛用于数据提取、报告生成、数据过滤等任务。在 UNIX 和类 UNIX 系统中,awk
是一种常用的命令行程序。
当你使用命令 awk '{print $2}'
时,你告诉 awk
读取输入文件的每一行,并打印出每行的第二个字段。这里的 $2
表示每行的第二个字段,字段之间通常由空白字符(如空格或制表符)分隔。
命令解释
awk
:命令本身。{print $2}
:这是awk
的动作部分,告诉awk
要执行的操作。print $2
表示打印每行的第二个字段。
使用示例
假设你有一个名为 data.txt
的文件,内容如下:
apple 10
banana 20
cherry 30
运行以下命令:
awk '{print $2}' data.txt
输出将会是:
10
20
30
更多选项
- 如果你只想打印第二列,并且每行的字段由逗号分隔,你可以使用
awk -F, '{print $2}'
。 - 你还可以通过指定不同的字段分隔符来处理更复杂的数据格式。例如,使用
awk -F"\t" '{print $2}'
来处理制表符分隔的字段。
其他用途
awk
也可以用于更复杂的文本处理任务,如条件判断、循环、数学运算等。例如,打印每行的第二字段,但只打印值大于15的行:awk '{if ($2 > 15) print $2}' data.txt
awk
是一个非常灵活的工具,适合处理各种文本数据。
7.写个shell脚本用于杀死运行的进程
当你运行程序时,shell脚本可以使用ps
命令查找到特定进程的PID并使用kill
命令杀死该进程。
以下是一个简单的Shell脚本示例,假设你的程序名为test
:
#!/bin/bash
# 查找进程PID
PID=$(ps aux | grep './test' | grep -v grep | awk '{print $2}')
if [ -n "$PID" ]; then
# 杀死进程
kill -SIGKILL $PID
echo "Process ./test (PID $PID) killed."
else
echo "Process ./test not found."
fi
这段脚本是一个 Bash 脚本,用于查找并可能终止名为 ./test
的进程。下面是对脚本的逐行分析:
-
#!/bin/bash
:- 这是一个 shebang 行,告诉操作系统使用
/bin/bash
来执行这个脚本。bash
是 Bourne Again Shell 的缩写,是许多 UNIX 和 Linux 系统上默认的 shell。
- 这是一个 shebang 行,告诉操作系统使用
-
# 查找进程PID
:- 这是一行注释,解释接下来的命令的作用。
-
PID=$(ps aux | grep './test' | grep -v grep | awk '{print $2}')
:- 这行命令执行了一系列管道操作来查找进程 ID(PID)。
ps aux
:列出当前运行的所有进程。grep './test'
:过滤出包含./test
的行,假设./test
是你想要查找的进程名称。grep -v grep
:过滤掉包含grep
的行,避免grep
命令本身也被匹配。awk '{print $2}'
:使用awk
打印每行的第二个字段,通常是进程的 PID。
- 这个命令的输出(即找到的 PID)被赋值给变量
PID
。
- 这行命令执行了一系列管道操作来查找进程 ID(PID)。
-
if [ -n "$PID" ]; then
:- 这是一个条件语句,检查变量
PID
是否不为空(即是否找到了进程)。
- 这是一个条件语句,检查变量
-
# 杀死进程
:- 这是另一行注释,说明接下来的命令的作用。
-
kill -SIGKILL $PID
:- 如果找到了进程(即
PID
不为空),则发送SIGKILL
信号强制终止进程。SIGKILL
是一个强制杀死进程的信号,进程无法捕获或忽略。
- 如果找到了进程(即
-
echo "Process ./test (PID $PID) killed."
:- 输出一条消息,告知用户进程已被杀死,并显示进程的 PID。
-
else
:- 如果条件语句中的条件不满足(即没有找到进程),则执行
else
块中的命令。
- 如果条件语句中的条件不满足(即没有找到进程),则执行
-
echo "Process ./test not found."
:- 输出一条消息,告知用户没有找到名为
./test
的进程。
- 输出一条消息,告知用户没有找到名为
-
fi
:- 结束
if
条件语句。
- 结束
这个脚本是一个简单的例子,展示了如何在 Bash 中使用条件语句和管道来处理系统任务。然而,需要注意的是,使用 kill -SIGKILL
强制终止进程可能会导致数据丢失或其他问题,因此在生产环境中应谨慎使用。此外,脚本中的 grep
命令可能需要根据实际情况调整,以确保正确匹配进程名称。