使用 mutt 在 Bash 中发送邮件及附件

在编写定时备份脚本时遇到一个需求,就是在 Bash 脚本中发送带附件的邮件。于是找到了 mutt。

Mutt 是一个命令行的邮件客户端,Mutt 能够轻松地在命令行发送和读取邮件。 Mutt 支持 POP 和 IMAP 协议。 尽管 Mutt 是基于命令的,但也同样有一个友好的界面。

如果不使用界面,使用 Mutt 命令发邮件也非常方便,只需要一条命令即可发送或者批量发送邮件。

功能说明

E-mail 管理程序。

语法

mutt [-hnpRvxz][-a 文件][-b 地址][-c 地址][-f 邮件文件][-F 配置文件][-H 邮件草稿][-i 文件][-m 类型][-s 主题] 邮件地址

补充说明:mutt 是一个文字模式的邮件管理程序,提供了全屏幕的操作界面。

参数:

  • -a 文件 在邮件中加上附加文件。
  • -b 地址 指定密件副本的收信人地址。
  • -c 地址 指定副本的收信人地址。
  • -f 邮件文件 指定要载入的邮件文件。
  • -F 配置文件 指定 mutt 程序的设置文件,而不读取预设的.muttrc 文件。
  • -h 显示帮助。
  • -H 邮件草稿 将指定的邮件草稿送出。
  • -i 文件 将指定文件插入邮件内文中。
  • -m 类型 指定预设的邮件信箱类型。
  • -n 不要去读取程序培植文件 (/etc/Muttrc)。
  • -p 在 mutt 中编辑完邮件后,而不想将邮件立即送出,可将该邮件暂缓寄出。
  • -R 以只读的方式开启邮件文件。
  • -s 主题 指定邮件的主题。
  • -v 显示 mutt 的版本信息以及当初编译此文件时所给予的参数。
  • -x 模拟 mailx 的编辑方式。
  • -z 与 -f 参数一并使用时,若邮件文件中没有邮件即不启动 mutt。

安装方法

Debian/Ubuntu/Linux Mint 安装

sudo apt-get install -y mutt

使用方法

发送一封简单的邮件(可能会被主流邮箱认为垃圾邮件,垃圾箱查看一下)

echo "Email body" | mutt -s "Email Title" root@einverne.info

进入命令行交互界面之后使用如下快捷键操作

  • 使用 t 改变接受者邮件地址
  • 使用 c 改变 Cc 地址
  • 使用 a 来增加附件
  • 使用 q 退出
  • 使用 y 来发送邮件

发送带附件的邮件

echo "body" | mutt -s "mysql backup" root@einverne.info -a /mysql.tar.gz

读取文本中的信息作为内容

mutt -s "Test" xxxx@qq.com

添加多个附件

echo "body" | mutt -s "web backup" root@einverne.info -a /mysql.tar.gz -a /web.tar.gz

抄送和密送

echo "body" | mutt -s "title" -c cc@gmail.com -b bcc@gmail.com root@einverne.info
  • 使用 -c 来抄送
  • 使用 -b 来添加密送
  • 使用 -s 来添加标题
  • 使用 -a 来添加附件

设置发件人

编辑配置文件

vi /etc/Muttrc

添加如下内容,防止被作为垃圾邮件

set from="mutt@einverne.info"
set use_from=yes
set envelope_from="yes"
set realname="Ein Verne"

mutt@einverne.info 为发信地址

mutt 发送邮件略慢,需要等待一分钟或者更长才能发送成功,作为备份工具好好利用。

reference


2017-10-17 linux , email , mutt

Podcast 频道推荐

Podcast 作为一种特殊的借助声音传播的媒体,有着特定的适用场景和范围。对于我来说每当我需要放空我自己,而又需要使用眼睛和手的时候,比如长时间走路,开车,再比如乘坐地铁等等情况下都会不自觉的打开一集播客。对于固定时间的上下班时间会听固定时长差不多在 1h 左右的内容,而其他时刻根据不同的时长可能会有短到 10 分钟,或者长 30 分钟的内容。

拿播客跟传统电台广播相比,播客的制作可能会更加精良,主题更加集中,信息密度更大一些,大部分往往是针对某一个话题进行介绍或者讨论。

推荐使用通用性客户端订阅收听,比如 Google Podcasts,或者 Pocket Casts 等等,后文有整理。

每期必听

反派影评

反派影评:个人看电影比较多,从偶然听到一期“观影风向标”开始就一直追随者波米到了“反派影评”,这些年陆陆续续也听过不少影评类节目,但只有反派让我一直坚持到现在仍然每期必听。如果你是一个观影爱好者,那就一定不能错过这一档节目。反派影评在 Podcast 上只更新长节目,短节目还是要上微信公众号的。

  • 主题内容:每期主题一部电影,根据这部电影展开,从导演到演员等等
  • 时长:1h 以上
  • 更新周期:每周,但时间不固定

Self-Hosted

Self-Hosted 是一个关于如何自建各种服务,将自己的数字生活以及产生的数据自我管理的播客。

东腔西调

[[东腔西调]] 是一档由大观天下制作的文化对谈类播客。主播邀请来自不同领域的嘉宾学者聊聊他们关心的世界和生活,探寻社会文化背后的来龙去脉。

Talk Python to me (by Michael Kennedy)

这是一个和 Python 相关的播客,但是又不止于 Python,还包括 Data Science,AI,Web development 等等话题。

  • 官网:https://talkpython.fm/
  • 主题内容:Python 相关
  • 时长: 1h 左右
  • 更新周期:大致为一周

This American Life

This American Life is an American weekly hour-long radio program produced in collaboration with Chicago Public Media and hosted by Ira Glass. It is broadcast on numerous public radio stations in the United States and internationally, and is also available as a free weekly podcast.

  • 主题内容:This American Life
  • 时长: over 1 hour
  • 更新周期:大致为一周

  • 688 期,首个获得普利策奖的音频节目

Talk to me in Korean

学习基础韩国语的节目

  • 主题内容:韩语基础会话
  • 时长: less than 10min
  • 更新周期:大致为一周

牛津人文通识解读

从传统意义上这应该算是讲座了,因为《[[通往奴役之路]]》而在 YouTube 上得知这一个主题的演讲,所以下载之后转成音频在路上听了。演讲者是西北政法大学的副教授谌洪果,因为一些缘故辞职了。所以我们才能在 YouTube 上看到这一个系列的讲座,主讲的材料是基于《牛津人文通识》这样一套书籍,这一系列的书大多都是哲学类,或者哲学家相关的书籍,当然也有比如现代日本,美国大法官之类。这一个系列的讲座每一期围绕一本书进行一段分享,YouTube 上的内容不全,不过也陆陆续续听了 24 期,可以说每一期都非常值得听,现在依然记忆深刻的有,哈耶克的,美国最高法院,现代日本等等话题。

  • 主题内容:围绕牛津人文通识读本每期分享一期
  • 时长:1h-2h
  • 更新周期:不固定

Acquired

Acquired 是一个关注科技圈成功的收购和上市的节目,两位主持人 David 和 Ben 在投资界都有一定的经验 1, 能够听出来每一期节目都有非常充足的材料。有的时候他们也会分析一些国内的成功投资经验,比如 Tencent,Xiaomi,Alibaba 等等,也有一些非常热门的话题比如 Tesla,GitHub etc。

  • 官网:https://www.acquired.fm/
  • 主题内容:Exploring, Analyzing, and Grading Technology Acquisitions and IPOs
  • 时长:2h 左右
  • 更新周期:Season 季播,更新时周更

内核恐慌

听“两个中老年人程序员” 聊天。就是更新时间非常不固定。

  • 主题内容:程序,Mac,设计,字体,软件包等等
  • 时长:100 分钟以上
  • 更新周期:节目更新时间不固定

疯投圈

内核恐慌主持人 Rio 的另一档节目,年初刚开始听,果然质量还是不错的。

  • 主题内容:投资,科技,消费,各行各业
  • 时长:60 分钟以上
  • 更新周期:节目更新时间不固定,一般月更

decoder

这是一档由 The Verge 推出的播客节目,听的第一期是因为想要了解更多关于 Airbnb 的内容,于是搜到了创始人 Brain Chesky 的访谈,发现这档节目不错

  • 主题内容:访谈,创业
  • 时长:60 分钟以上
  • 更新周期:周更

故事 FM

听不同人讲自己的故事。

  • 主题内容:不定
  • 时长:小于 30 min
  • 更新周期:每 3 天左右

日本自由行

青岛刘伟品质旅游主播的一档节目,主播是一位从事多年日本旅游工作的导游,从日本文化,历史,饮食,等等方面对日本做出一些介绍。当时就是随心所听,节目不长,声音录制设备也不是很好,但是内容短小精湛非常不错,在主播的推荐下陆陆续续接触了日本文化,甚至看了《日本论》《静观日本》等等书籍。

主播本人是一位职业导游,从 90 十年代开始从事中日导游,翻译工作,给国人定制深度旅游,也接待日方游客。节目内容重在文化,历史,从历史文化中了解日本人的礼仪,美学等等方面。

  • 主题内容:日本文化,旅游,自由行
  • 时长:30 分钟左右
  • 更新周期:前期节目不固定,后面固定为每周三更新

非每期必听但值得一提

星箭广播

当时去了解 Medium 的创始人的时候搜索到的播客,后面陆陆续续还听了关于 Google Map 创始人的专题,是一档非常不错的、有一定深度的播客。后面就挑着感兴趣的继续听了。

牛油果烤面包

硅谷从业人员聊科技相关的话题,在一些不熟悉的领域上给了我开阔眼界的机会。

东亚观察局

一档中日韩三国记者的线上聊天节目,围绕着三国不同的政治、文化展开。

忽左忽右

主题不固定,主要在于媒体,投资,互联网等等

  • 主题内容:不固定
  • 时长:30 分钟到 1 小时不定
  • 更新周期:不固定

静说日本 徐静波

或许直接去看讲述日本的书,要比直接听这个要好很多。个人不是非常喜欢这个频道。

  • 主题内容:日本
  • 时长:20 分钟以下
  • 更新周期:大概每周

百靈果 NEWS

这是一档台湾人做的双语国际新闻平台,当然听到后期就会发现主播们打着双语的旗号,其实更多的是在“聊天”,或者用他们自己的话说,就是“讲干话”。当然继续收听他们的节目主要是主播二人非常有趣。

国际新闻,双语,读书,闲聊,台湾

  • 主题内容:不固定
  • 时长:30 分钟到 1 小时不定
  • 更新周期:不固定

陌声人

每一期会分享一本图书

一席

朋友推荐,听了多抓鱼创始人的一集,确实不错,可以选择自己喜欢的话题和主题来听。通常情况下一集大概半个小时左右,会邀请以为专业人士就某一个话题进行演讲,看成是国内复刻版的 TED 吧。

  • 主题内容:
  • 时长:
  • 更新周期:

Breaking into Board Games

看名字就知道是和桌游相关的内容了

  • 主题内容:
  • 时长:
  • 更新周期:

翻转电台

一档和哲学,政治,经济,平民文化相关的电台

  • 主题内容:哲学、政治、经济、平民文化
  • 时长:30min~1h 不等
  • 更新周期:每周

InSession Film Podcast

一档英语影评节目

  • 主题内容:电影,演员,导演
  • 时长:1h 以上不等
  • 更新周期:Every 2 or 3 days.

政见合集

时政

选 美

美国政治

欧罗万象

NPR Up first

综合新闻

The Daily

纽约时报的 The Daily 专题报道

The Dropout

ABC The Dropout 听深度报道

欧洲政局变化

Planet Money

这档节目来自 NPR,绝对的明星节目。每期不到 30 分钟,把抽象的经济理论和政治决议与日常生活的种种现象结合,或者通过一个普通的生活事例分析出里面蕴含的经济学道理。听这档节目会上瘾。

  • 502 集,棉花

Freakonomics Radio

Freakonomics Radio 是一个经济话题相关的播客,里面总是会提到很多经济概念。

ChooseFI

ChooseFI [[FIRE]] 运动

Mad Fintest

Mad Fintest

All in Podcast

All In Podcast 四位投资人。

曾经听过但个人不是很推荐

每个人都个人的喜好,网上很多 list 也不都是我最喜欢的博客,我觉得能够找到方法找到自己喜欢的某一类播客就是很好的,授人以鱼不如授人以渔,就是这样,但是这些我听过,并不是非常喜欢的播客我也列出来,喜不喜欢可以自行判断。

  • 代码时间
  • 电影不无聊
  • 闲侃日本
  • 新闻酸菜馆

Podcast 客户端推荐

早之前在 Android 的时候买过 Pocket Cast 可是竟然发现这家每个平台都要收费,不管是 Web ,还是 Android/iOS 客户端,每一个都是单独收费,尽管能够同步订阅列表,但后来转用 iOS 也就放弃了这个客户端,我用过的几个还不错的

  • Podcasts 苹果自身的客户端,虽然设计和使用都不是那么美观和人性化,但只要订阅了就会默默在 wifi 下帮你下载好,不用每一次上路了才想起来
  • Castbox,一款新出的客户端,客户端全免费,甚至网页版还提供博客平台的托管,虽然国内使用速度略慢,但是是我用过的里面体验比较好的了

至于其他人推荐的 Castro, Overcast 如果不缺钱的话买就行了。个人觉得重要的是内容,还有同步的功能,我希望我订阅的内容跟着账号走,而不是跟着设备走。其他国内的 app 比如 喜马拉雅,蜻蜓,荔枝 FM, 听伴 就凭着个人喜好选择即可。

至于为什么不推荐国内的这些 FM,是因为本来 Podcast 是一个公开分享的渠道,发布到 Podcast 上的内容可以被全世界的人访问到,而国内的 FM 都是闭门造车,封闭自己的用户,限制用户只能在他们的平台上传,听众也只有他们平台的账号才能收听,所有的内容就像是自家花园,外人不得入内。而放到 iTunes 的 Podcast 就像是放到了博物馆,全世界的人只要觉得有用就都能够收听到。而且封闭的环境导致审查异常严格,甚至聊电影都能碰到审查,同样也就导致了低智化内容泛滥,内容越没有思考越能得到平台的推荐,自此循环往复,推荐榜单就几乎没有能听的内容了。而在 Podcast 中反而一些很小众的 Channel 内容质量异常高,在某一个专业领域分享的知识,内容足够填满整个频道。

Podcast Hosting

其他站点

一个关于博客统计信息的站点

  • https://chartable.com/
  • https://github.com/guipdutra/awesome-geek-podcasts#in-chinese
  • https://wanqu.co/b/57/2017-03-03-podcasts.html

reference


2017-10-17 podcast , audio , castbox , pocket-cast , google-podcasts

在 Spring Boot 中使用 Swagger 生成接口文档

在使用 Spring Boot 构建一套 RESTful 接口的时候经常需要手工维护一份接口文档以提供给不同的客户端使用,有的时候手工维护成本太高,今天发现了一套自动化生成 RESTful 接口文档的工具 Swagger 。

Swagger 能根据 Spring Controller 接口自动生成一个文档页面,在代码中使用注解将接口文档注释,非常方便。 Swagger 整合到 Spring boot 项目中也非常方便。

添加依赖

io.springfox >= 3.0

<dependency>
 <groupId>io.springfox</groupId>
 <artifactId>springfox-boot-starter</artifactId>
 <version>3.0.0</version>
</dependency>

访问地址是:http://localhost:8080/swagger-ui/#/

pom.xml 中添加

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.7.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.7.0</version>
</dependency>

最新的版本可以在 mvnrepository 上查到,或者上官网或者 github。

添加配置类

在项目 package 根下新建如下 Class:

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket helloApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("info.einverne.springboot.demo"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                // 文档标题
                .title("API 文档")
                // 文档描述
                .description("https://github.com/einverne/thrift-swift-demo/tree/master/spring-boot-demo")
                .termsOfServiceUrl("https://github.com/einverne/thrift-swift-demo/tree/master/spring-boot-demo")
                .version("v1")
                .build();
    }
}

通过@Configuration注解,让 Spring 来加载该类配置。再通过@EnableSwagger2注解来启用 Swagger2。

  • apiInfo() 用来创建 API 的基本信息,展现在文档页面中。
  • select() 函数返回一个 ApiSelectorBuilder 实例用来控制哪些接口暴露给 Swagger ,这里使用定义扫描包路径来定义, Swagger 会扫描包下所有 Controller 的定义并产生文档内容,除了被 @ApiIgnore 注解的接口。

添加接口注释

@ApiOperation 注解来给 API 增加说明、通过 @ApiImplicitParams@ApiImplicitParam 注解来给参数增加说明。

一个简单的注释

@ApiOperation(value = "创建用户", notes = "根据 User 对象创建用户")
@ApiImplicitParam(name = "user", value = "用户详细实体 user", required = true, dataType = "User")
@RequestMapping(value = "", method = RequestMethod.POST)
public String postUser(@RequestBody User user) {
    users.put(user.getId(), user);
    return "success";
}

详细的例子可以参考源代码 https://github.com/einverne/thrift-swift-demo

再添加注释后启动 Spring boot, 访问 http://localhost:8080/swagger-ui.html 即可看到 API 文档

Api 注解

@Api 注解用于类上,说明类作用

  • value url
  • description
  • tags 设置该值,value 会被覆盖
  • basePath 基本路径不可配置
  • position
  • produces “application/json”
  • consumes “application/json”
  • protocols http, https, wss
  • authorizations 认证
  • hidden 是否在文档隐藏

ApiOperation 注解

标记在方法上,对一个操作或 HTTP 方法进行描述。具有相同路径的不同操作会被归组为同一个操作对象。不同的 HTTP 请求方法及路径组合构成一个唯一操作。此注解的属性有:

  • value 对操作的简单说明,长度为 120 个字母,60 个汉字。
  • notes 对操作的详细说明。
  • httpMethod HTTP 请求的动作名,可选值有:”GET”, “HEAD”, “POST”, “PUT”, “DELETE”, “OPTIONS” and “PATCH”。
  • code 默认为 200,有效值必须符合标准的 HTTP Status Code Definitions

ApiImplicitParams

注解 ApiImplicitParam 的容器类,以数组方式存储。

ApiImplicitParam

对 API 的单一参数进行注解。虽然注解 @ApiParam 同 JAX-RS 参数相绑定,但这个 @ApiImplicitParam 注解可以以统一的方式定义参数列表,也是在 Servelet 及非 JAX-RS 环境下,唯一的方式参数定义方式。注意这个注解 @ApiImplicitParam 必须被包含在注解 @ApiImplicitParams 之内。可以设置以下重要参数属性:

  • name 参数名称
  • value 参数的简短描述
  • required 是否为必传参数
  • dataType 参数类型,可以为类名,也可以为基本类型(String,int、boolean 等)
  • paramType 参数的传入(请求)类型,可选的值有 path, query, body, header or form。

@RequestBody 这样的场景请求参数无法使用 @ApiImplicitParam 注解进行描述

ApiParam

增加对参数的元信息说明。这个注解只能被使用在 JAX-RS 1.x/2.x 的综合环境下。其主要的属性有:

  • required 是否为必传参数
  • value 参数简短说明

ApiModel

提供对 Swagger model 额外信息的描述。在标注 @ApiOperation 注解的操作内,所有的类将自动被内省(introspected),但利用这个注解可以做一些更加详细的 model 结构说明。主要属性有:

  • value model 的别名,默认为类名
  • description model 的详细描述

ApiModelProperty

对 model 属性的注解,主要的属性值有:

  • value 属性简短描述
  • example 属性的示例值
  • required 是否为必须值
  • hidden 隐藏该属性

ApiResponse

响应配置

  • code http 状态码
  • message 描述

ApiResponses

多个 Response

验证机制

考虑到安全的问题,每次请求 API 需要对用户进行验证与授权。目前主流的验证方式采用请求头部(request header)传递 token,即用户登录之后获取一个 token,然后每次都使用这个 token 去请求 API。如果想利用 swagger-UI 进行 API 测试,必须显式为每个需要验证的 API 指定 token 参数。这时可以为每个操作添加一个注解 @ApiImplicitParams,具体代码如下:

@ApiImplicitParams({@ApiImplicitParam(name = "TOKEN", value = "Authorization token", required = true, dataType = "string", paramType = "header")})

根据环境选择开启 Swagger

Swagger 提供了 enable 方法,可以通过设置该方法来选择开启 Swagger 来在线上环境禁用 Swagger。

@Bean
public Docket customImplementation(){
    return new Docket(SWAGGER_2)
        .apiInfo(apiInfo())
        .enable(environmentSpeficicBooleanFlag) //<--- Flag to enable or disable possibly loaded using a property file
        .includePatterns(".*pet.*");
}

如果使用 Spring @Profile 也可以

@Bean
@Profile("production")
public Docket customImplementation(){
    return new Docket(SWAGGER_2)
        .apiInfo(apiInfo())
        .enable(false) //<--- Flag set to false in the production profile
        .includePatterns(".*pet.*");
}

From: https://stackoverflow.com/a/27976261/1820217

reference


2017-10-16 spring , spring-boot , swagger , java , restful-api , api-doc

每天学习一个命令: Linux 查看磁盘信息命令 di

平时在 Linux 上查看磁盘信息都使用 df -lh , -l 显示本地文件系统, -h 来表示 human readable 。虽然 df 在一定程度上能够满足查询磁盘剩余空间的需求,但是这里要介绍一款强大于 df 的命令 —- di 。

使用如下命令安装

sudo apt install di

di 命令是 disk information 的缩写,直接运行 di 会有如下结果

di
Filesystem         Mount               Size     Used    Avail %Used  fs Type
/dev/sda1          /                 901.2G   188.3G   667.1G   26%  ext4   
tmpfs              /dev/shm            7.8G     0.1G     7.6G    2%  tmpfs  
tmpfs              /run                1.6G     0.1G     1.5G    4%  tmpfs  
cgmfs              /run/cgmanager/     0.1M     0.0M     0.1M    0%  tmpfs  
tmpfs              /run/lock           5.0M     0.0M     5.0M    0%  tmpfs  
tmpfs              /run/user/0         1.6G     0.0G     1.6G    0%  tmpfs  
tmpfs              /run/user/1000      1.6G     0.0G     1.6G    0%  tmpfs  
tmpfs              /sys/fs/cgroup      7.8G     0.0G     7.8G    0%  tmpfs  
/dev/sda1          /var/lib/docker   901.2G   188.3G   667.1G   26%  ext4   

di 默认的输出就是比较人性化的了。

看 di 的使用介绍 man di 就会发现 di 是这么介绍自己的

> di Displays usage information on mounted filesystems.  Block values are reported in a human readable format.  If the user or group has  a  disk  quota,  the  values  reported  are adjusted according the quotas that apply to the user.

一些简单的使用

A 选项打印所有挂载设备

di -A
Mount                fs Type Filesystem 
	Options                                                           
	    Size     Used     Free %Used  %Free 
	    Size     Used    Avail %Used  %Free 
	    Size     Used    Avail %Used  
	   Inodes     IUsed     IFree %IUsed
/                    ext4    /dev/sda1  
	rw,relatime,errors=remount-ro,data=ordered                        
	  901.2G   188.3G   712.9G   21%    79%  
	  901.2G   234.1G   667.1G   26%    74%  
	  855.4G   188.3G   667.1G   22%  
	 60014592   1372538  58642054    2% 
/dev/shm             tmpfs   tmpfs      
	rw,nosuid,nodev                                                   
	    7.8G     0.1G     7.6G    2%    98%  
	    7.8G     0.1G     7.6G    2%    98%  
	    7.8G     0.1G     7.6G    2%  
	  2036725       741   2035984    0% 
/run                 tmpfs   tmpfs      
	rw,nosuid,noexec,relatime,size=1629380k,mode=755                  
	    1.6G     0.1G     1.5G    4%    96%  
	    1.6G     0.1G     1.5G    4%    96%  
	    1.6G     0.1G     1.5G    4%  
	  2036725       777   2035948    0% 
/run/cgmanager/fs    tmpfs   cgmfs      
	rw,relatime,size=100k,mode=755                                    
	    0.1M     0.0M     0.1M    0%   100%  
	    0.1M     0.0M     0.1M    0%   100%  
	    0.1M     0.0M     0.1M    0%  
	  2036725        14   2036711    0% 
/run/lock            tmpfs   tmpfs      
	rw,nosuid,nodev,noexec,relatime,size=5120k                        
	    5.0M     0.0M     5.0M    0%   100%  
	    5.0M     0.0M     5.0M    0%   100%  
	    5.0M     0.0M     5.0M    0%  
	  2036725         4   2036721    0% 
/run/user/0          tmpfs   tmpfs      
	rw,nosuid,nodev,relatime,size=1629380k,mode=700                   
	    1.6G     0.0G     1.6G    0%   100%  
	    1.6G     0.0G     1.6G    0%   100%  
	    1.6G     0.0G     1.6G    0%  
	  2036725         4   2036721    0% 
/run/user/1000       tmpfs   tmpfs      
	rw,nosuid,nodev,relatime,size=1629380k,mode=700,uid=1000,gid=1000 
	    1.6G     0.0G     1.6G    0%   100%  
	    1.6G     0.0G     1.6G    0%   100%  
	    1.6G     0.0G     1.6G    0%  
	  2036725        36   2036689    0% 
/sys/fs/cgroup       tmpfs   tmpfs      
	rw,mode=755                                                       
	    7.8G     0.0G     7.8G    0%   100%  
	    7.8G     0.0G     7.8G    0%   100%  
	    7.8G     0.0G     7.8G    0%  
	  2036725        18   2036707    0% 
/var/lib/docker/aufs ext4    /dev/sda1  
	rw,relatime,errors=remount-ro,data=ordered                        
	  901.2G   188.3G   712.9G   21%    79%  
	  901.2G   234.1G   667.1G   26%    74%  
	  855.4G   188.3G   667.1G   22%  
	 60014592   1372538  58642054    2% 

c 选项逗号分割

使用 -c 选项分割输出

di -c
s,m,b,u,v,p,T
"/dev/sda1","/","901.2G","188.3G","667.1G",26%,"ext4"
"tmpfs","/dev/shm","7.8G","0.1G","7.6G",2%,"tmpfs"
"tmpfs","/run","1.6G","0.1G","1.5G",4%,"tmpfs"
"cgmfs","/run/cgmanager/fs","0.1M","0.0M","0.1M",0%,"tmpfs"
"tmpfs","/run/lock","5.0M","0.0M","5.0M",0%,"tmpfs"
"tmpfs","/run/user/0","1.6G","0.0G","1.6G",0%,"tmpfs"
"tmpfs","/run/user/1000","1.6G","0.0G","1.6G",0%,"tmpfs"
"tmpfs","/sys/fs/cgroup","7.8G","0.0G","7.8G",0%,"tmpfs"
"/dev/sda1","/var/lib/docker/aufs","901.2G","188.3G","667.1G",26%,"ext4"

c 是 --csv-output 的缩写,为了便于程序解析

t 参数增加统计行

-t 参数在最后增加统计行

di -t
Filesystem         Mount               Size     Used    Avail %Used  fs Type
/dev/sda1          /                 901.2G   188.4G   667.0G   26%  ext4   
tmpfs              /dev/shm            7.8G     0.1G     7.6G    2%  tmpfs  
tmpfs              /run                1.6G     0.1G     1.5G    4%  tmpfs  
cgmfs              /run/cgmanager/     0.1M     0.0M     0.1M    0%  tmpfs  
tmpfs              /run/lock           5.0M     0.0M     5.0M    0%  tmpfs  
tmpfs              /run/user/0         1.6G     0.0G     1.6G    0%  tmpfs  
tmpfs              /run/user/1000      1.6G     0.0G     1.6G    0%  tmpfs  
tmpfs              /sys/fs/cgroup      7.8G     0.0G     7.8G    0%  tmpfs  
/dev/sda1          /var/lib/docker   901.2G   188.4G   667.0G   26%  ext4   
                   Total               1.8T     0.4T     1.3T   26%      

s 参数对结果排序

di -s 默认更具 mount point 输出

  • di -sm 默认 mount pont
  • di -sn 不排序,按照挂载表 /etc/fstab 中顺序
  • di -ss 按照特殊设备
  • di -st 根据 filesystem type
  • di -sr 逆序输出

排序方式可以组合使用,如:di –stsrm :按照类型、设备、挂载点逆序排序。 di –strsrm :按照类型、设备逆序、挂载点逆序排序。

f 选项自定义格式

di -fM
Mount               
/                   
/dev/shm            
/run                
/run/cgmanager/fs   
/run/lock           
/run/user/0         
/run/user/1000      
/sys/fs/cgroup      
/var/lib/docker/aufs

只打印 mount point

更多的 f 的选项可以直接参看 man di

总结

虽然 di 提供了比 df 更多更强大的功能,但是也有其缺点,大部分的 Linux 发行版默认是没有预装的。


2017-10-16 linux , 磁盘管理 , disk , df

SonarQube continuous code quality

关键字提取,开源,代码质量管理,多语言支持。

SonarQube is an open-source platform developed by SonarSource for continuous inspection of code quality to perform automatic reviews with static analysis of code to detect bugs, code smells, and security vulnerabilities on 20+ programming languages.

开源协议:Lesser GNU General Public License

SonarQube 可以从以下七个维度检测代码质量,而作为开发人员至少需要处理前 5 种代码质量问题

  • 不遵循代码标准 SonarQube 可以通过 PMD,CheckStyle,Findbugs 等等代码规则检测工具规范代码编写。
  • 潜在的缺陷 SonarQube 可以通过 PMD,CheckStyle,Findbugs 等等代码规则检测工具检测出潜在的缺陷。
  • 糟糕的复杂度分布 文件、类、方法等,如果复杂度过高将难以改变,这会使得开发人员难以理解它们,且如果没有自动化的单元测试,对于程序中的任何组件的改变都将可能导致需要全面的回归测试。
  • 重复 显然程序中包含大量复制粘贴的代码是质量低下的,SonarQube 可以展示源码中重复严重的地方。
  • 注释不足或者过多 没有注释将使代码可读性变差,特别是当不可避免地出现人员变动时,程序的可读性将大幅下降 而过多的注释又会使得开发人员将精力过多地花费在阅读注释上,亦违背初衷。
  • 缺乏单元测试 SonarQube 可以很方便地统计并展示单元测试覆盖率。
  • 糟糕的设计 通过 SonarQube 可以找出循环,展示包与包、类与类之间的相互依赖关系,可以检测自定义的架构规则 通过 SonarQube 可以管理第三方的 jar 包,可以利用 LCOM4 检测单个任务规则的应用情况, 检测耦合。

reference


2017-10-11

jenkins setup and introduction

Jenkins 是什么

Jenkins 是一个独立的开源自动化服务器,可用于自动化各种任务,如构建,测试和部署软件。Jenkins 可以通过本机系统包 Docker 安装,甚至可以通过安装 Java Runtime Environment 的任何机器独立运行。

Jenkins 有什么作用

用于持续、自动构建,测试项目,监控外部任务运行等。

模板类型

新建模板类型

  • Freestyle project 基础功能,执行构建任务
  • Pipeline,真实工作环境可能会包含,代码检查,编译,单元测试,部署等等任务,这个模板就是串行执行任务
  • Multi-configuration project job 跑在不同的机器上

问题

master/slave 节点配置

  • https://www.howtoforge.com/tutorial/ubuntu-jenkins-master-slave/

2017-10-10 jenkins , ci-cd , ci

mockito 使用

单元测试的目的是在不涉及依赖的情况下测试代码(隔离)。一个设计良好的系统需要遵循 SOLID 原则。

  • (S) Single responsibility principle 单一职责
  • (O) Open/closed principle 开闭原则,对修改关闭,对扩展开放
  • (L) Liskov substitution principle 里氏替换原则,子类和父类表现一致
  • (I) Interface segregation principle 接口隔离原则,不要依赖不要使用的方法,或者在设计时尽量将大接口拆开
  • (D) Dependency inversion 依赖反转,将类和其他类隔离开,尽量依赖抽象,而不依赖具体实现,比如当修改了外部外部依赖具体实现,而不需要大规模的修改原始代码

Target and challenge of unit testing

Unit test 目标是针对一个模块或者一段代码隔离测试,应该消除其他类,或者系统依赖带来的副作用。

测试替身(Test Doubles)用来消除这类副作用,test Doubles 可以分为:

  • dummy object 传入不使用
  • fake object 有基本实现,但是非常简单,比如内存数据库
  • stub class 带着特殊目的的接口或者类的部分实现
  • mock object 接口或者类的虚假实现,可以用来定义固定的输出,mock object 在测试中用来执行特定的行为

通常情况下可以通过手工代码来 mock objects 或者使用 mock framework 来模拟类的行为。Mockito 是一个非常流行的 Mock framework,使用 Mockito 的三段论:

  • Mock 外部依赖,插入 mock 代码
  • 执行 unit test
  • 验证输出

Maven

http://search.maven.org 中搜索 a:"mockito-core" 或者 g:"org.mockito"

比如说增加:

<dependency>
	<groupId>org.mockito</groupId>
	<artifactId>mockito-all</artifactId>
	<version>1.9.5</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.11</version>
	<scope>test</scope>
</dependency>

mockito-all

mockito-all 是一个包含了所有依赖 (hamcrest 和 objenesis) 的单一 jar 包。

mockito-core

mockito-core 不包含 hamcrest 和 objenesis 依赖,可以自己来控制依赖的版本。

启用注解

第一种方法,使用 JUnit 的 @RunWith

@RunWith(MockitoJUnitRunner.class)
public class MockitoAnnotationTest {
    ...
}

或者用代码启用

@Before
public void init() {
    MockitoAnnotations.initMocks(this);
}

@Mock 注解

最常用的注解就是 @Mock 注解,使用 @Mock 注解来创建和插入 mocked 实例,这样就省去了手动调用 Mockito.mock() 方法。

@Spy 注解

@Spy 注解可以 mock 真实对象的方法,让真实对象方法被调用时,就像 mock object 一样可以被配置。

@Captor 注解

参数捕获器,用于捕获 mock 方法参数,进行验证使用

@InjectMocks 注解

@InjectMocks 注解会自动将 mock fields 注入到被测试的 object 中。

需要注意的是,在 JUnit 4 中必须使用 @RunWith(MockitoJUnitRunner.class)MockitoAnnotations.initMocks(this) 来初始化 mocks 并注入。

在 JUnit 5 中必须使用 @ExtendWith(MockitoExtension.class)

@RunWith(MockitoJUnitRunner.class) // JUnit 4
// @ExtendWith(MockitoExtension.class) for JUnit 5
public class SomeManagerTest {

    @InjectMocks
    private SomeManager someManager;

    @Mock
    private SomeDependency someDependency; // this will be injected into someManager
 
     // tests...

}

when thenReturn

通过 when().thenReturn() 方法链可以用来指定一个方法调用预先定义好的返回值。这个方法也可以指定抛出一个异常

Properties properties = mock(Properties.class);

when(properties.get(”Anddroid”)).thenThrow(new IllegalArgumentException(...));

try {
    properties.get(”Anddroid”);
    fail(”Anddroid is misspelled”);
} catch (IllegalArgumentException ex) {
    // good!
}

thenReturn vs thenAnswer 区别

当 mock 方法是知道确定的返回值,那么可以使用 thenReturn 或者 doReturn,方法会 mock 一个确定的返回值。

thenReturn(T value) Sets a return value to be returned when the method is called.

Answer 当需要根据不同条件来 mock 方法并且返回不同返回值时需要 Answer,比如需要根据方法传入参数来返回对应的返回值的情况。

Mock 静态方法

Mockito 暂时还不支持 Mock 静态方法,所以需要借助 jmockit 完成静态方法的 Mock:

<dependency>
  <groupId>org.jmockit</groupId>
  <artifactId>jmockit</artifactId>
  <version>1.30</version>
  <scope>test</scope>
</dependency>


new MockUp<StaticClass>() {
  @mockit.Mock
  public boolean isXXX() {
    return true;
  }
};

Other

《Mockito Cookbook》参考代码:https://github.com/marcingrzejszczak/mockito-cookbook

reference


2017-09-27 mockito , unit-test , java , mock

从 Clonezilla 恢复系统学习 Linux 启动过程

最近又一次使用 Clonezilla 来克隆系统,和以往不同的是,这一次我是备份了整块硬盘到镜像,然后从镜像恢复系统到另外一块硬盘,而不是以往是复制一个分区,所以又产生了一些问题,所以有了这篇文章,一方面来记录一下中间遇到的问题,另一方面也学习巩固一下关于 Linux 启动过程中的必要流程。

基础知识

关于最基础的计算机启动过程也就不展开说,阮一峰,和网上大部分文章已经讲的非常清晰了,这里只简单的列举一些基础名词,必须要知道的概念。

BIOS

全称是 Basic Input Output System,也就是一块被写入开机程序的只读内存,电脑通电之后第一时间会读取的芯片。

POST

POST 不是 HTTP 请求方法的 POST,而是 Power On Self Test,开机自检,在通电之后 BIOS 加载后会自动执行。

MBR

[[MBR]] 是主引导分区,全称是 Master Boot Record,该分区决定该设备是否能够启动。如果设备能够启动,那么该设备第一个扇区,512 字节就需要表明该设备能够启动。

主引导记录告诉计算机去哪一块硬盘寻找操作系统,主引导记录由三部分组成

  • 1-446 字节,调用系统机器码
  • 447-510 字节,分区表
  • 511-512 字节,主引导记录签名 (0x55,0xAA)。

mbr

GRUB

[[GRUB]] 是 Linux 下最流行的启动管理器(Boot Loader),全称是 GRand Unified Bootloader。计算机在读取 MBR 前 446 字节机器码之后,将运行事先安装的启动管理器 boot loader 也就是 GRUB。

GRUB 设计兼容 multiboot specification,为了使得 GRUB 能够引导启动各种版本的 Linux。GRUB 也能够让用户选择从不同的 kernels 启动。Grub 的配置文件地址在 /boot/grub/grub.conf.

GRUB1 现在正在被 GRUB2 代替,Grub2 在 /boot/grub2/grub.cfg 配置。

所以总结来看,计算机通电之后,先通过 BIOS,到 MBR,通过 GRUB 引导操作系统启动。以上就是计算机 boot sequence 的过程。

System startup

通过 GRUB 引导启动操作系统,之后操作系统就会接管系统启动,操作系统启动过程也分为很多步骤。

Stage 1.0

BIOS 在 MBR 中搜索 boot record,因为主引导分区必须保证尽量小,所以通过 stage 1.0 找到 1.5 GRUB 引导,GRUB 引导必须在 boot record 和第一个分区之间,将 GRUB 加载到内存之后进入 stage 1.5.

Stage 1.5

上面提到 GRUB 必须在引导记录和磁盘第一个分区之间,这一块区域因为历史原因被预留,磁盘的第一个分区从 sector 63 开始,MBR 在 sector 0,所以留下了 62512 byte sectors,大概 31774 bytes,用来保存 core.img, core.img 只有 25389 字节,所以完全足够。

需要注意的是,/boot 目录需要存放在支持 GRUB 的文件系统上。stage 1.5 在找到 /boot 文件和加载必要的驱动之后执行。

Stage 2

GRUB stage 2 阶段会加载 Linux kernel 到内存中,kernel 和相关的文件在 /boot 目录中。kernel 文件都以 vmlinuz 开头。

kernel 都是压缩的格式以节省空间,当计算机解压并加载内核到内存之后,会加载 systemd,自此系统启动过程结束,内核和 systemd 都已经在运行。

systemd 是所有进程的创始者,他负责将系统启动至一个可工作的状态,他负责包括挂载系统文件,启动管理系统服务等等工作。

systemd 启动流程

首先 systemd 会尝试使用 /etc/fstab 中定义的内容挂载文件系统,包括定义的根分区,swap 分区文件等等。

# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
# / was on /dev/sda1 during installation
UUID=3d1b7e3e-c184-4664-9555-2b088997f2c8 /              ext4    errors=remount-ro 0       1
# swap was on /dev/sda5 during installation
UUID=b99bf592-a25b-4ca0-b597-fc62e121aae1 none          swap    sw            0    0

关于 systemd 的启动顺序,可以参考阮一峰的这篇

修改 GRUB

默认情况下 GRUB 会有 10 秒时间来给用户选择系统,这个设置可以通过修改 /etc/default/grub 来修改。

GRUB_DEFAULT=0          # 默认启动的系统序号
GRUB_TIMEOUT=4          # 默认等待多久以预设系统开机

更新之后使用 sudo update-grub 来更新 GRUB。

GRUB Manual 手册中提供了完整的参数解释。

遇到的问题

No bootable device

使用 Clonezilla 复制硬盘所有分区到另外一块硬盘,而我这边遇到的情况是复制结束之后硬盘没有 boot 分区,导致 BIOS 无法找到主引导分区。

解决办法是使用 Clonezilla 的专家(高级)模式,在高级模式中会自动修复 grub 的问题

无法启动 Linux Mint 桌面

这个问题表现形式可能是各种各样的,开机黑屏,或者在 grub 引导之后出现各种乱码命令。对于这个问题的解决方法可能需要是修改 /etc/fstab ,将其中硬盘的 UUID ,通过 sudo blkid 查看获取后保证 /etc/fstab 中启动的硬盘是一块。可以参考之前的文章

reference


2017-09-25 linux , boot , mbr , uefi , bios , clonezilla

每天学习一个命令:使用 grep 查找文件内字符串

一直都知道 grep 很强大,但是一直都没有办法来定义它,直到看到 man 介绍第一行,非常简洁精炼 “print lines matching a pattern”,一下子就知道了 grep 的作用。

grep 全称是 Global Regular Expression Print。grep 的工作方式是这样的,它在一个或多个文件中搜索字符串模板。如果模板包括空格,则必须被引用,模板后的所有字符串被看作文件名。搜索的结果被送到标准输出,不影响原文件内容。

grep 可用于 shell 脚本,因为 grep 通过返回一个状态值来说明搜索的状态,如果模板搜索成功,则返回 0,如果搜索不成功,则返回 1,如果搜索的文件不存在,则返回 2。我们利用这些返回值就可进行一些自动化的文本处理工作。

grep 名字的由来

UNIX 早期的编辑器中,如果要查找比如 junk 这个单词,需要输入 /junk/p 来打印找到的一个行,如果要找所有行,就使用 g/junk/p,g 是 global search 的意思。

这个功能被独立出来作为了命令,只做一件事情,就是全局搜索,并打印,叫做 grep,其实是 g/regular expression/p 的缩写,可以理解为 g/re/p,在 vi 或者 sed 中类似的功能也经常能见到。

必须知道的使用方式

下面这些选项非常常用,记住:

-i 忽略大小写
-v invert match
-n 行号

-B NUM -A NUM
-C NUM

使用实例

比如说在当前目录下,搜索文件中包含的 password 词

grep password *

grep 会自动在当前目录下搜索并将包含 password 行打印出来,所以千万不要在本地文件中存放密码一类的敏感信息,这一无异于将密码写在显示器上。

忽略大小写匹配

使用 -i 来忽略大小写

grep -i password *

这一行命令会匹配,比如 Password,PASSWORD 等等。

将 grep 作为过滤器来过滤标准输出的内容

cat /etc/passwd | grep 'sshd'

强制让 grep 输出文件名

grep 'sshd' /etc/passwd /dev/null

这时 grep 会打印文件名,冒号,结果

显示行号

-n 参数会显示行号

grep -n pattern file.txt

显示不包含正则的行

正常情况下 grep 会过滤出匹配 正则的行,使用 -v 参数反之,显示不包含正则的行

grep -v 'junk' *

再比如,如果想要设置 every 但是不想搜索 everyoneeverybodyeverywhere 可以使用

grep every * | grep -v one | grep -v body | grep -v where

显示匹配行前后的行

grep -B 10 -A 20 pattern file.txt

输出匹配 PATTERN 行的前 10 行和 后 20 行。

打印一行中正则匹配的部分

很多时候一行日志中有非常多的内容,我们往往只关心特定的部分,这个时候可以使用 grep 的正则来过滤出我们关心的部分,比如日志中的耗时我们可能会打印出 cost 10ms 这样的内容

grep -o -P "cost [0-9]+ms"

说明:

  • -o 表示只输出匹配到的内容,每一行显示一个
  • -P "regex" 表示开启正则过滤

当然如果组合使用 sed 也能够做到同样的事情。

扩展

在 grep 之上,后人又开发了很多很有用的命令,比如不解压的情况下搜索压缩包中的内容,再比如简便版的 ack-grep

reference


2017-09-23 grep , linux , egrep , command , regular

jinja2 笔记

jinja2 是基于 Python 的模板引擎。

种类

注意下面的 \ 是因为模板需要转义,使用时需要去掉,另外大括号和 % 之间的空格也需要去掉。

\{\% ... \%\}  Statements 控制结构,比如 if/elif/else for-loops,macros,block 等等
\{\{ ... \}\}  Expressions 表达式,用来输出结果
\{\# ... \#\}  Comments 注释
\#  ... \#\#  Line Statements

表达式及过滤器

\{\{ name \}\} 结构表示一个变量

过滤器用法

Hello, \{\{ name|capitalize \}\}

常用的过滤器

过滤器 说明
safe 渲染值时不转义
capitalize 把值的首字母转换成大写,其他字母转换成小写
lower 把值转换成小写形式
upper 把值转换成大写形式
title 把值中每个单词的首字母都转换成大写
trim 把值的首尾空格去掉
striptags 渲染之前把值中所有的 HTML 标签都删掉
length 输出长度

完整的过滤器清单可以在文档中找到:http://jinja.pocoo.org/docs/templates/#builtin-filters

控制结构

用来控制流程

常见的 if-else

{ % if user % }
    Hello, !
{ % else % }
    Hello, Stranger!
{ % endif % }

for 循环

<ul>
{ % for comment in comments % }
    <li></li>
{ % endfor % }
</ul>

宏,类似方法

{ % macro render_comment(comment) % }
    <li></li>
{ % endmacro % }

<ul>
{ % for comment in comments % }
    
{ % endfor % }
</ul>

保存到文件中使用 import

{ % import 'macros.html' as macros % }
<ul>
    { % for comment in comments % }
        
    { % endfor % }
</ul>

需要多次使用的模板可以单独放在文件中,然后 使用 include

{ % include 'common.html' % }

模板继承,先定义父模板

<html>
<head>
    { % block head % }
        <title>{ % block title % }{ % endblock % } - My Application</title>
    { % endblock % }
</head>
<body>
{ % block body % }
{ % endblock % }
</body>
</html>

然后继承

{ % extends "base.html" % }
{ % block title % }Index{ % endblock % }
{ % block head % }
    
    <style>
    </style>
{ % endblock % }
{ % block body % }
    <h1>Hello, World!</h1>
{ % endblock % }

表单

WTForms 支持的字段类型

字段类型 说明
StringField 文本字段
TextAreaField 多行文本字段
PasswordField 密码文本字段
HiddenField 隐藏文本字段
DateField 文本字段,值为 datetime.date 格式
DateTimeField 文本字段,值为 datetime.datetime 格式
IntegerField 文本字段,值为整数
DecimalField 文本字段,值为 decimal.Decimal
FloatField 文本字段,值为浮点数
BooleanField 复选框,值为 True 和 False
RadioField 一组单选框
SelectField 下拉列表
SelectMultipleField 下拉列表,可选择多个值
FileField 文件上传字段
SubmitField 表单提交按钮
FormField 把表单作为字段嵌入另一个表单
FieldList 一组指定类型的字段

WTForms 支持的验证函数

验证函数 说明
Email 验证电子邮件地址
EqualTo 比较两个字段的值;常用于要求输入两次密码进行确认的情况
IPAddress 验证 IPv4 网络地址
Length 验证输入字符串的长度
NumberRange 验证输入的值在数字范围内
Optional 无输入值时跳过其他验证函数
Required 确保字段中有数据
Regexp 使用正则表达式验证输入值
URL 验证 URL
AnyOf 确保输入值在可选值列表中
NoneOf 确保输入值不在可选值列表中

在模板中使用

<form method="POST">

 

</form>

2017-09-22 python , flask , jinja2 , template , web

电子书

本站提供服务

最近文章

  • 从 Buffer 消费图学习 CCPM 项目管理方法 CCPM(Critical Chain Project Management)中文叫做关键链项目管理方法,是 Eliyahu M. Goldratt 在其著作 Critical Chain 中踢出来的项目管理方法,它侧重于项目执行所需要的资源,通过识别和管理项目关键链的方法来有效的监控项目工期,以及提高项目交付率。
  • AI Shell 让 AI 在命令行下提供 Shell 命令 AI Shell 是一款在命令行下的 AI 自动补全工具,当你想要实现一个功能,敲一大段命令又记不住的时候,使用自然语言让 AI 给你生成一个可执行的命令,然后确认之后执行。
  • 最棒的 Navidrome 音乐客户端 Sonixd(Feishin) Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。 Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。
  • 中心化加密货币交易所 Gate 注册以及认证 Gate.io 是一个中心化的加密货币交易所。Gate 中文通常被称为「芝麻开门」,Gate 创立于 2013 年,前身是比特儿,是一家致力于安全、稳定的数字货币交易所,支持超过 1600 种数字货币的交易,提供超过 2700 个交易对。
  • 不重启的情况下重新加载 rTorrent 配置文件 因为我在 Screen 下使用 rTorrent,最近经常调试修改 rtorrent.rc 配置文件,所以想要找一个方法可以在不重启 rTorrent 的情况重新加载配置文件,网上调查了一下之后发现原来挺简单的。