平时开发环境使用的是 jetty,而 Java Web 有一个更好更快的服务器 Resin,这篇文章就来说一下什么是 Resin,以及在 Debug 中如何使用。
Resin 是一个提供高性能的,支持 Java/PHP 的应用服务器。目前有两个版本:一个是 GPL 下的开源版本,提供给一些爱好者、开发人员和低流量网站使用;一种是收费的专业版本,增加了一些更加适用于生产环境的特性。
Resin 也可以和许多其他的 web 服务器一起工作,比如 Apache Server 和 IIS 等。Resin 支持 Servlets 2.3 标准和 JSP 1.2 标准。熟悉 ASP 和 PHP 的用户可以发现用 Resin 来进行 JSP 编程是件很容易的事情。Resin 支持负载平衡,可以增加 WEB 站点的可靠性。方法是增加服务器的数量。比如一台 Server 的错误率是 1% 的话,那么支持负载平衡的两个 Resin 服务器就可以使错误率降到 0.01%。到目前为止,Resin 对 WEB 应用的支持已经远远超过 Tomcat 等各种大型的 Server。
在 Resin 的官方 quick start 教程中有各大平台详细的安装指导。我在使用 apt 安装时没有成功,这里就记录下手工安装的过程。
在 http://caucho.com/download 网址下载, Resin 有两个版本, Pro 版和 GPL 开源版,个人使用开源基础版本已经足够。安装过程如下:
JAVA_HOME
中tar -vzxf resin-4.0.x.tar.gz
根据下载的最新版本解压cd resin-
./configure
更多参数参考 configure optionsmake
sudo make install
sudo resinctl start
, 或者运行 java -jar lib/resin.jar start
http://localhost:8080
可以使用 sudo resinctl stop
来停止运行,更多内容可以参考官方指南 。
第一步,添加 Resin Local 选项,在 IDEA 中 Run/Debug Configuration
中添加 Resin Local 选项
点击 configure 按钮,在弹出窗 Application Servers 中选择部分一中安装的 Resin 目录路径和目录下 Resin 的配置文件路径。
Run/Debug Configurations 中 Server 页面配置,基本都是默认。
Run/Debug Configurations 中 Deployment 页面配置,注意红色方框部分选择。选择 resin.xml 而不是 JMX 否则项目的 index 路径是 localhost:8080/appname/ 而不是 localhost:8080/
在 Resin 的服务器配置下 Depolyment 中 Depolyment method:有 JMX 和 resin.xml 两种选择,JMX 是把项目打包的文件放在 resin 服务器下 webapp 下 只有在服务器启动时 才把项目给拷贝过去 无法在 intellij 中实时更新;resin.xml 是在 C 盘 Temp 目录下 copy 了一份 resin.xml 的配置文件 然后把服务器目录空间指向了你的项目工作空间 可以实现 IntelliJ 修改实时更新。IntelliJ 默认的选择是 JMX 所以我们要选中 resin.xml 模式。同时当项目 Artifacts 指向的目录是 ROOT 时 上图中的 Use default context name(always true if depolyment method is JMX) 取消勾选
执行 build,得到 war 文件。执行 resin run/debug,会自动在你选择的浏览器中打开项目 index 页面。也可以在 IDEA 下方的 Application Servers 面板中进行 Resin 的启动,停止等操作。Resin 启动的打印信息也在此窗口显示。
在 Python 中调用系统命令可以使用两种方法,一种是 os 模块中的 system() 方法,一种是 subprocess 模块中的 call() 方法。
这个方法会接受一个字符串命令,然后在 subshell 中执行,通常是 linux/OSX 下的 bash ,或者 Windows 下面的 cmd.exe。根据官方的文档,os.system()
方法时使用标准 C 方法 system() 来调用实现的,所以存在和 C 方法中一样的限制。
os.system("python –version")
举例
import os
cmd = "git --version"
returned_value = os.system(cmd) # returns the exit code in unix
print('returned value:', returned_value)
默认情况下不使用 shell,他只是简单的执行传入的字符串
运行带参数的命令需要传 list
subprocess.call(["python", "–version"])
subprocess.call() 当使用 shell=True 时和 os.system() 一样使用 shell
subprocess.call(["python", "–version"], shell=True)
举例
import subprocess
cmd = "date"
# returns output as byte string
returned_output = subprocess.check_output(cmd)
# using decode() function to convert byte string to string
print('Current date is:', returned_output.decode("utf-8"))
subproecess 模块中如果想要实现更加复杂的命令,可以使用 Popen()。popen() 会创建一个管道,fork 一个子进程,然后该子进程执行命令。返回值在标准 IO 流中,该管道用于父子进程间通信。父进程要么从管道读信息,要么向管道写信息,至于是读还是写取决于父进程调用 popen 时传递的参数(w 或 r)。
import subprocess
child = subprocess.Popen(['ping','-c','4','douban.com'])
child.wait()
print 'main process'
默认情况下,开启子进程之后不会等待 child 执行完成,需要使用 wait() 来等待子进程完成。
父进程对子进程还有其他的操作
child.poll() # 检查子进程状态
child.kill() # 终止子进程
child.send_signal() # 向子进程发送信号
child.terminate() # 终止子进程
子进程的标准输入、标准输出和标准错误
child.stdin
child.stdout
child.stderr
举例
import subprocess
child1 = subprocess.Popen(["ls","-l"], stdout=subprocess.PIPE)
print child1.stdout.read()
使用 subprocess 创建子进程能够重定向 stdin stdout,总体来说要比 os.system() 更加强大。
最近因为好奇所以大致的看了一下 Telegram 的 bot,很早 就开始用 Telegram 但事实上,我大部分情况下就是将 Telegram 作为一个跨平台同步工具和备份工具来使用,直到最近因为 Ingress 的一些朋友和一个图书群我对 Telegram 才有了更多的使用,也在 Telegram 发现了每周会推送一本书的 Channel。
所以我想照着有一个叫做 to_kindle_bot 的号自己写了一个将图书发送到 kindle 邮箱的机器人,bot 要实现的功能很简单,就是将图书资源发送给 bot 之后将图书发送邮件推送到关联的 kindle 邮箱。将任务拆解开,主要的步骤也比较简单
再做的过程中,我又增加了一些功能
所以一一解决这些问题即可。
这一部分其实也没有什么要多说的,botFather 会创建好 bot,然后我用 Python 的 python-telegram-api。基本上也都是非常成熟的文档。
因为早之前就已经做了一个图书相关的网站,所以理所当然的将用 telegram uid 和用户的 kindle 邮箱做了以下映射。
这部分其实之前也做过,所以直接使用 Flask mail 或者标准库里面的 smtp 去发就行了,邮件服务早之前 也做过调查,也是现成的用 mailgun 了。
这一部分确实花了我一些精力,使用 python 的 ebooklib 库来解析了 epub 格式的电子书,也因为 epub 格式鱼龙混杂,所以修了几个 bug,但是 epub 格式的图书解析确实解决了,但是 mobi 格式我是没有找到任何方式来解析,不过幸好,调研的过程中发现了 Calibre 自带的 ebook-meta 命令行工具,在测试之后发现非常好用,支持的格式也非常多,几乎能够支持 99% 的情况,那这样一来获取图书 meta 和封面的工作也 OK。
这部分一开始准备想要用 Amazon 提供的 kindlegen 命令行工具来实现的,但是发现他只能够将 其他的格式转为 mobi 格式,那这样局限就比较大了,虽然能够 cover 我现在的需求,但是如果以后有 mobi 格式转 epub 格式的需求,其实就有了问题,不够幸好还是 Calibre 这个强大的工具,提供了 ebook-convert 命令行工具,能够转化非常多的格式,所以理所当然的就采用了它。
至此就完成了 bot 90% 的工作,剩下就是组织一些文案和 bot 的零星的 bug 问题,所以可以试用下 @kindlepush_bot。
bot 现在为止一共有 start
help
email
settings
这几个功能,除开 settings 功能还未完成, email 命令用来设置 kindle 邮箱,start 和 help 命令用来显示帮助,settings 功能我设想可以让用户选择一个频率,bot 会自动在这个频率下发送一本书到 kindle 邮箱中。
另外如果大家喜欢电子书,我用 Flask 练手的电子书网站 https://book.einverne.info 也欢迎使用。
Jstack 用于打印出给定的 java 进程 ID 或 core file 或远程调试服务的 Java 堆栈信息。
这里需要注意的是 Java 8 引入了 Java Mission Control,Java Flight Recorder,和 jcmd
等工具来帮助诊断 JVM 和 Java 应用相关的问题。推荐使用最新的工具以及 jcmd
来进行诊断。
Prints Java thread stack traces for a Java process, core file, or remote debug server. This command is experimental and unsupported.
jstack 命令能够:
如果 java 程序崩溃生成 core 文件,jstack 工具可以用来获得 core 文件的 java stack 和 native stack 的信息,从而可以轻松地知道 java 程序是如何崩溃和在程序何处发生问题。另外,jstack 工具还可以附属到正在运行的 java 程序中,看到当时运行的 java 程序的 java stack 和 native stack 的信息,如果运行的 java 程序呈现 hung 的状态,jstack 是非常有用的。
thread dump 就是将当前时刻正在运行的 JVM 的线程拷贝一份,可以用来分析程序执行情况。
打印某个进程的堆栈信息
jstack [PID]
jstack -l [PID]
jstack -m [PID]
jstack -F [PID]
关于如何找到 PID,有很多方法,使用 jps -v
或者 ps -aux
或者 htop
等等方法都可以。
说明:
-l
选项会打印额外的信息,比如说锁信息, locks such as a list of owned java.util.concurrent ownable synchronizers,可以查看 AbstractOwnableSynchronizer
-F
Force a Stack Dump在执行 jstack -l [PID] > /tmp/output.txt
之后可以对 /tmp/output.txt
进行分析
jstack 输出开头是当前 dump 的时间和 JVM 基本信息(包括版本等):
2018-05-24 14:41:06
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.101-b13 mixed mode):
下面这个部分是 Safe Memory Reclamation(SMR) 安全的内存分配信息:
Threads class SMR info:
_java_thread_list=0x00007f3cd4005870, length=30, elements={
0x00007f3d14011800, 0x00007f3d142dd800, 0x00007f3d142e1800, 0x00007f3d142f4000,
0x00007f3d142f6000, 0x00007f3d142f8000, 0x00007f3d142fa000, 0x00007f3d14333800,
0x00007f3d14340000, 0x00007f3d14bc6800, 0x00007f3c900a1000, 0x00007f3c90255000,
0x00007f3c9025e800, 0x00007f3c90264000, 0x00007f3d14bdf800, 0x00007f3c64008800,
0x00007f3c6400b000, 0x00007f3d14c1e800, 0x00007f3c54025800, 0x00007f3c54027000,
0x00007f3c54042800, 0x00007f3c54044800, 0x00007f3c24005800, 0x00007f3c0c008800,
0x00007f3c0c00a000, 0x00007f3c0c00b800, 0x00007f3c48027000, 0x00007f3c48010000,
0x00007f3c48011000, 0x00007f3cd4004800
}
接下来就是程序的线程信息(非 VM 线程,非 GC 线程):
"main" #1 prio=5 os_prio=0 cpu=1071286.79ms elapsed=509136.64s tid=0x00007f3d14011800 nid=0xad5 runnable [0x00007f3d1993a000]
java.lang.Thread.State: RUNNABLE
at org.eclipse.swt.internal.gtk.OS.Call(Native Method)
at org.eclipse.swt.widgets.Display.sleep(Display.java:5570)
at smartgit.Wx.d(SourceFile:305)
at com.syntevo.smartgit.n.a(SourceFile:398)
at com.syntevo.smartgit.n.a(SourceFile:247)
"Reference Handler" #2 daemon prio=10 os_prio=0 cpu=94.43ms elapsed=509136.51s tid=0x00007f3d142dd800 nid=0xadc waiting on condition [0x00007f3cf10f4000]
java.lang.Thread.State: RUNNABLE
at java.lang.ref.Reference.waitForReferencePendingList(java.base@11.0.3/Native Method)
at java.lang.ref.Reference.processPendingReferences(java.base@11.0.3/Reference.java:241)
at java.lang.ref.Reference$ReferenceHandler.run(java.base@11.0.3/Reference.java:213)
Locked ownable synchronizers:
- None
线程信息又可以划分成几个部分。
Section | Example | 解释 |
---|---|---|
线程名字 | main 和 Reference Handler | 可读的线程名字,这个名字可以通过 Thread 方法 setName 设定 |
线程 ID | #1 | 每一个 Thread 对象的唯一 ID,这个 ID 是自动生成的,从 1 开始,通过 getId 方法获得 |
是否守护线程 | daemon | 这个标签用来标记线程是否是守护线程,如果是会有标记,如果不是这没有 |
优先级 | prio=10 | Java 线程的优先级,可以通过 setPriority 方法设置 |
OS 线程的优先级 | os_prio | |
CPU 时间 | cpu=94.43ms | 线程获得 CPU 的时间 |
elapsed | elapsed=509136.51s | 线程启动后经过的 wall clock time |
Address | tid | Java 线程的地址,这个地址表示的是 JNI native Thread Object 的指针地址 |
OS 线程 ID | nid | The unique ID of the OS thread to which the Java Thread is mapped. |
线程状态 | wating on condition | 线程当前状态 线程状态下面就是线程的堆栈信息 |
Locked Ownable Synchronizer |
线程的运行状态:
New
: 线程对象创建,不可执行Runnable
: 调用 thread.start() 进入 runnable,获得 CPU 时间即可执行Running
: 执行Waiting
: thread.join()
或调用锁对象 wait()
进入该状态,当前线程会保持该状态直到其他线程发送通知到该对象Timed_Waiting
:执行 Thread.sleep(long)、thread.join(long) 或 obj.wait(long) 等就会进该状态,与 Waiting 的区别在于 Timed_Waiting 的等待有时间限制;Blocked
: 等待锁,进入同步方法,同步代码块,如果没有获取到锁会进入该状态。该线程尝试进入一个被其他线程占用的 synchronized 块,当前线程直到锁被释放之前一直都是 blocked 状态Dead
:执行结束,或者抛出了未捕获的异常之后Deadlock
: 死锁Waiting on condition
:等待某个资源或条件发生来唤醒自己Waiting on monitor entry
:在等待获取锁terminated
线程已经结束 run()
并且通知其他线程 joining以上内容来自 Oracle
通过 jstack 信息可以分析线程死锁,或者系统瓶颈,但是这篇文章比较粗浅,只介绍了大概,等以后熟悉了补上。
HandlerMethodArgumentResolver 是一个接口:
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter var1);
@Nullable
Object resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;
}
HandlerMethodArgumentResolver 的接口定义如下:
关于 HandlerMethodArgumentResolver 执行流程,大部分可以在类 InvocableHandlerMethod 中看到
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 获取 Controller 中函数的参数对象
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(this.getMethod(), this.getBeanType()) + "' with arguments " + Arrays.toString(args));
}
Object returnValue = this.doInvoke(args);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Method [" + ClassUtils.getQualifiedMethodName(this.getMethod(), this.getBeanType()) + "] returned [" + returnValue + "]");
}
return returnValue;
}
private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 获取执行的具体函数的参数
MethodParameter[] parameters = this.getMethodParameters();
Object[] args = new Object[parameters.length];
for(int i = 0; i < parameters.length; ++i) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = this.resolveProvidedArgument(parameter, providedArgs);
if (args[i] == null) {
// 首先判断是否有参数解析器支持参数 parameter,采用职责链的设计模式
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
// 如果参数解析器支持解析参数 parameter,那么解析参数成 Controller 的函数需要的格式
args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
} catch (Exception var9) {
if (this.logger.isDebugEnabled()) {
this.logger.debug(this.getArgumentResolutionErrorMessage("Failed to resolve", i), var9);
}
throw var9;
}
} else if (args[i] == null) {
throw new IllegalStateException("Could not resolve method parameter at index " + parameter.getParameterIndex() + " in " + parameter.getExecutable().toGenericString() + ": " + this.getArgumentResolutionErrorMessage("No suitable resolver for", i));
}
}
}
return args;
}
AbstractNamedValueMethodArgumentResolver 抽象类主要用来解析方法参数 (resolving method arguments from a named value),请求参数,请求头,path 变量都是 named value 的例子。他下面有很多的子类
AbstractCookieValueMethodArgumentResolver
ExpressionValueMethodArgumentResolver
MatrixVariableMethodArgumentResolver
PathVariableMethodArgumentResolver
RequestAttributeMethodArgumentResolver
RequestHeaderMethodArgumentResolver
RequestParamMethodArgumentResolver
ServletCookieValueMethodArgumentResolver
SessionAttributeMethodArgumentResolver
PathVariableMethodArgumentResolver 用来解析注解 @PathVariable
的参数。
@PathVariable
用来解析 URI 中的 Path 变量,Path 变量是必须的,并且没有默认值。
用来将请求参数解析到注解 @RequestParam
修饰的方法参数中。
比如请求 https://localhost:8080/hello?uid=1234
@RequestMapping(value="/hello",method=RequestMethod.POST)
@ResponseBody
public Map<String,Object> test(@RequestParam(value = "uid", required = true, defaultValue = "1") String uid){
return m;
}
其中将请求的 uid 值解析到方法参数 uid 就是通过 RequestParamMethodArgumentResolver 来实现的。
AbstractMessageConverterMethodArgumentResolver 是一个抽象类,主要用来将请求 body 中的内容解析到方法参数中,其子类:
代码片段
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
protected final List<HttpMessageConverter<?>> messageConverters;
protected final List<MediaType> allSupportedMediaTypes;
}
AbstractMessageConverterMethodProcessor 的子类 RequestResponseBodyMethodProcessor:支持 @RequestBody
和 @ResponseBody
,使用举例:
@RequestMapping(value="/hello/test",method=RequestMethod.POST)
@ResponseBody
public Map<String,Object> test(@RequestBody Map<String,Object> m){
return m;
}
antigen 是 zsh 的插件管理工具,在他 GitHub 主页上的一句话非常形象的解释了他的功能。
Antigen is to zsh, what Vundle is to vim.
curl -L git.io/antigen > antigen.zsh
或者
apt-get install zsh-antigen
或者直接 git clone 该项目,然后指定 antigen.zsh 的位置。
如果使用过 Vim 的 Vundle 对 antigen 的配置应该不陌生。
source /path-to-antigen-clone/antigen.zsh
# Load the oh-my-zsh's library.
antigen use oh-my-zsh
# Bundles from the default repo (robbyrussell's oh-my-zsh).
antigen bundle git
antigen bundle heroku
antigen bundle pip
antigen bundle lein
antigen bundle command-not-found
# Syntax highlighting bundle.
antigen bundle zsh-users/zsh-syntax-highlighting
# Load the theme.
antigen theme robbyrussell
# Tell antigen that you're done.
antigen apply
使配置生效 source ~/.zshrc
可以从这个页面 查看更多的插件。
更加详细的配置可以参考我的配置
在安装 antigen 之后可以直接在命令行输入 antigen version
来查看版本。或者使用其他命令来直接安装插件,更新插件等等。
➜ antigen version
apply -- Load all bundle completions
bundle -- Install and load the given plugin
bundles -- Bulk define bundles
cache-gen -- Generate cache
cleanup -- Clean up the clones of repos which are not used by any bundles currently lo
help -- Show this message
init -- Load Antigen configuration from file
list -- List out the currently loaded bundles
purge -- Remove a cloned bundle from filesystem
reset -- Clears cache
restore -- Restore the bundles state as specified in the snapshot
revert -- Revert the state of all bundles to how they were before the last antigen up
selfupdate -- Update antigen itself
snapshot -- Create a snapshot of all the active clones
theme -- Switch the prompt theme
update -- Update all bundles
use -- Load any (supported) zsh pre-packaged framework
version -- Display Antigen version
部分内容从 Python 3 网络爬虫开发实战 书 中整理。
主要依赖 Python 模块
在程序中经常要使用到 Unix timestamp 和日期的转换,通常情况下都是 Google 一个时间戳转换的网页在进行转换,其实 Linux 命令中就有能够快速实现转换的命令。主要都是集中在 date 这个命令。date 命令主要用于显示或设定系统时间和日期。
Linux 用来修正系统的时区
sudo dpkg-reconfigure tzdata
选择 Asia > Shanghai
date +%s # 返回 10 位时间戳,%s 表示从 1970-01-01 0 点 (epoch 开始的秒数)
date +%s%3N # 返回 13 位时间戳,毫秒
date +%s%N # 返回 10 + 9 位纳秒
$ date +%s
1504516338
$ date -d @1504516338
Mon Sep 4 17:12:18 CST 2017
使用 -d
参数可以用来将输入 String 转成特定格式日期,如果不指定具体时间,date 会使用 00:00:00
$ date -d "06/04/1989"
Sun Jun 4 00:00:00 CDT 1989 1559192456
$ date -d "04 June 1989"
Sun Jun 4 00:00:00 CDT 1989
$ date -d "June 04 1989"
Sun Jun 4 00:00:00 CDT 1989
$ date -d "June 04 1989 12:01:01"
Sun Jun 4 12:01:01 CDT 1989
-d
选项也有一些其他很强大的功能,比如
$ date -d '5 minutes ago' # 5 分钟前的时间
Mon Sep 4 17:22:58 CST 2017
$ date -d '100 days' # 100 天以后的日期
Wed Dec 13 17:29:14 CST 2017
$ date -d '-100 days' # 100 天以前的日子
Sat May 27 17:30:01 CST 2017
$ date -d '100 days ago' # 同上
Sat May 27 17:31:10 CST 2017
$ date -d 'next monday'
Mon Sep 11 00:00:00 CST 2017
或者 -d 选项还可以有这样的语法
date -d@1559192456
可以使用 + 来输出不同格式
date +%<format options>
比如
$ date '+%Y-%m-%d %H:%M:%S'
2017-09-04 17:38:46
Format options | Purpose of Option | Output |
date +%a | 缩略方式显示星期 (like Mon, Tue, Wed) | Thu |
date +%A | 全称显示星期 (like Monday, Tuesday) | Thursday |
date +%b | Displays Month name in short (like Jan, Feb, Mar ) | Feb |
date +%B | Displays Month name in full short (like January, February) | February |
date +%d | Displays Day of month (e.g., 01) | 07 |
date +%D | Displays Current Date; shown in MM/DD/YY | 02/07/13 |
date +%F | Displays Date; shown in YYYY-MM-DD | 2013-02-07 |
date +%H | Displays hour in (00..23) format | 23 |
date +%I | Displays hour (01..12) format | 11 |
date +%j | Displays day of year (001..366) | 038 |
date +%m | Displays month (01..12) | 02 |
date +%M | Displays minute (00..59) | 44 |
date +%S | Displays second (00..60) | 17 |
date +%N | Displays nanoseconds (000000000..999999999) | 573587606 |
date +%T | Displays time; shown as HH:MM:SS Note: Hours in 24 Format | 23:44:17 |
date +%u | Displays day of week (1..7); 1 is Monday | 4 |
date +%U | Displays week number of year, with Sunday as first day of week (00..53) | 05 |
date +%Y | Displays full year i.e. YYYY | 2013 |
date +%Z | alphabetic time zone abbreviation (e.g., EDT) | IS |
我已经用了很长一段时间 Vim 了,但是 Vim 最可贵之处便在于你永远达不到 Vim 的天花板,在使用的过程中我永远会发现操作 Vim 的其他便捷方法。最近看了一个关于 Vim 的讲座 ,革新我对 Vim 命令的认识。可以说掌握这样的一个世界观可以对 Vim 的操作上到另外一个层次。下面就总结一下这个视频中的精髓内容。
@ChrisToomey 定义了一种 Vim Language,Vim 的语法由数词 + 动词 + 名词
组成,比如:
d 删除
w 单词
将两个字母组合起来就是 删除单词
这个经常使用的命令非常容易记住。如果想要删除三个单词,则是 3dw
,所以可以总结出
{number}{command}{text-object}
这样的形式
重复和撤销,相信使用过一段时间 Vim 的人应该会知道 .
表示重复上一次命令, u
表示撤销上一次操作。而重复和撤销是针对命令而不是针对输入的,因此每使用一次 .
或者 u
都是命令级别。因此这就给予了 .
操作非常强大的功能。
Verbs: 常用的动作举例
d Delete
c Change delete and enter insert mode
y yank
> intend 缩进
v 选择
Nouns: 常见的动作 Motion
w 移动到下一个 word 开始
e 移动到下一个 word 的结尾
b 移动到上一个 word 的开始 back
2j 向下移动 2 lines
Vim 中定义了很多移动操作
基于内容 Nouns: Text Objects
w => words 表示移动一个单词
s => sentences 移动一个句子
p => paragraphs 向下移动一个段落
t => tags (html xml)
动作 motions
a => all
i => in
t => 'till
f => find forward
F => find backward
iw => inner word
it => inner tag
i" => inner quotes
ip => inner paragraph
as => a sentence
命令 commands
d => delete(also cut)
c => change(delete, then into insert mode)
y => yank (copy)
v => visual select
组合举例
diw delete in word,即使光标在 word 中也能够快速删除 word
yi) yank all text inside the parentheses,光标在 `()` 中复制括号中的所有内容
上面的 Text Object
{command}{text object or motion}
在单词中间,diw
删除光标下的单词,dit
删除光标下 tag 中的内容
Nouns: Parameterized Text Objects
f,F => find the next character
t,T => find till
/,? => search
比如有一行文本
the program print ("Hello, World!")
如果想要删除 Hello 前面的内容,可以在行首使用 dtH
, 解读这个命令可以理解为 d => delete unti H
从这里删除直到遇到 H。典型的 verb + noun 组合。
记住动作加移动,能够很快的记忆很多命令。
比如使用 https://github.com/tpope/vim-surround 这个插件,可以轻松的实现,使用命令 cs"'
来将
"hello world"
变成
'hello world'
命令的语义解释就是 change surroundding double quote to single quote 将包围的双引号变成单引号
使用 cs'<p>
将单引号变成 <p>
<p>hello world</p>
使用 cst"
将 surrounding 变成双引号
"hello world"
或者可以使用 ds"
来将 surrounding 双引号删除 delete surrounding “
hello world
Vim 中的 “.” 命令,以命令为单位,重复上一个命令。
Sublime , IntelliJ IDEA 中经常被人提及的 multiple cursor 的功能,能够在编辑器中提供多个光标一起编辑,其实 Vim 不需要多光标就能够做到,结合强大的 .
和 n .
可以快速的编辑大量重复的内容。
比如在 Vim 中的 workflow 就是
/pattern
来所有需要编辑的内容cw
当前单词 ESC
退出,一个完成的动作n
来找到下一个匹配的地点.
来重复编辑操作,可以直接将单词替换为上一个动作编辑Vim 允许记录一个宏来完成一组命令
qa # 将命令记录到寄存器 a 中
q # 再次 q 结束记录
@a # 使用寄存器
@@ # 使用上一次寄存器
一些相关的命令
sudo fdisk -l # 列出磁盘分区表
结果是这样的:
Disk /dev/ram0: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram1: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram2: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram3: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram4: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram5: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram6: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram7: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram8: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram9: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram10: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram11: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram12: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram13: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram14: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram15: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/mmcblk0: 7.4 GiB, 7948206080 bytes, 15523840 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x1fdbda7f
Device Boot Start End Sectors Size Id Type
/dev/mmcblk0p1 8192 93813 85622 41.8M c W95 FAT32 (LBA)
/dev/mmcblk0p2 94208 15523839 15429632 7.4G 83 Linux
Disk /dev/sda: 1.4 TiB, 1500301909504 bytes, 2930277167 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x4c63688c
Device Boot Start End Sectors Size Id Type
/dev/sda1 2048 2930272255 2930270208 1.4T 7 HPFS/NTFS/exFAT
在最后可以看到一块磁盘 /dev/sda1
。
然后可以使用如下的方式手动挂载。
为了让 Linux 能够读取 NTFS 格式的硬盘,需要安装 ntfs-3g
。然后可以手动挂载。
sudo apt-get install ntfs-3g
sudo mkdir -p /media/sda1
sudo mount -t ntfs-3g /dev/sda1 /media/sda1
挂载完成后可以查看
sudo df -h
Filesystem Size Used Avail Use% Mounted on
/dev/root 7.3G 2.2G 4.8G 31% /
devtmpfs 460M 0 460M 0% /dev
tmpfs 464M 0 464M 0% /dev/shm
tmpfs 464M 13M 452M 3% /run
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 464M 0 464M 0% /sys/fs/cgroup
/dev/mmcblk0p1 42M 21M 21M 51% /boot
tmpfs 93M 0 93M 0% /run/user/1000
/dev/sda1 1.4T 1.1T 363G 75% /media/sda1
通过编辑 fstab 来让系统自动挂载
sudo vim /etc/fstab
插入
/dev/sda1 /mnt/hdd ext4 defaults 0 0
从而实现 USB 设备的自动挂载
sudo vim /etc/udev/rules.d/10-usbstorage.rules
KERNEL!="sd*", GOTO="media_by_label_auto_mount_end"
SUBSYSTEM!="block",GOTO="media_by_label_auto_mount_end"
IMPORT{program}="/sbin/blkid -o udev -p %N"
ENV{ID_FS_TYPE}=="", GOTO="media_by_label_auto_mount_end"
ENV{ID_FS_LABEL}!="", ENV{dir_name}="%E{ID_FS_LABEL}"
ENV{ID_FS_LABEL}=="", ENV{dir_name}="Untitled-%k"
ACTION=="add", ENV{mount_options}="relatime,sync"
ACTION=="add", ENV{ID_FS_TYPE}=="vfat", ENV{mount_options}="iocharset=utf8,umask=000"
ACTION=="add", ENV{ID_FS_TYPE}=="ntfs", ENV{mount_options}="iocharset=utf8,umask=000"
ACTION=="add", RUN+="/bin/mkdir -p /media/%E{dir_name}", RUN+="/bin/mount -o $env{mount_options} /dev/%k /media/%E{dir_name}"
ACTION=="remove", ENV{dir_name}!="", RUN+="/bin/umount -l /media/%E{dir_name}", RUN+="/bin/rmdir /media/%E{dir_name}"
LABEL="media_by_label_auto_mount_end"
如果要卸载文件系统,比如将挂载的 /media/sda1
卸载
umount /media/sda1