164. zotero软件
插件
- DOI Manager
- Jasminum
- Sci-Hub Plugin for Zotero
- Zotfile
- zotero-updateifs
Linux 命令是使用终端与系统交互的好方法。但是,有时可能需要一段时间才能完成手头的任务。这迫使用户等待相当长的时间或完全生成一个新的 shell。
幸运的是,您可以通过一些简单的方法在后台运行 Linux 命令。
在你的命令后添加一个 &
运行 Linux 后台命令的最简单方法是在命令后添加与号 (&)。例如,如果从终端启动 gedit 文本编辑器,则在关闭编辑器之前无法使用 shell。但是,当您在命令中添加额外的 & 时,您将能够立即使用 shell。
1 | $ gedit & |
对于需要长时间执行的任务,&符号可以使当前终端窗口不被占用,这样就能继续在同一个终端上工作,甚至关闭终端窗口也不会影响任务的正常执行。不过需要注意的是,如果要求这个任务输出内容到标准输出中(例如 echo 或 ls),即便使用了 &,也会等待这些输出任务在前台运行完毕;当使用 &将一个进程放置到后台运行的时候,Bash 会提示这个进程的进程 ID。在 Linux 系统中运行的每一个进程都有一个唯一的进程 ID,我们可以使用进程 ID 来暂停、恢复或者终止对应的进程。
Linux 中 的nohup命令允许管理员运行不受挂断(Hang Up)信号影响的终端命令。您可以使用 nohup 在后台运行 Linux 命令。
nohup 的一个主要好处是,即使您退出 shell,您的命令也会运行。此外,它会生成执行的日志文件。在当前目录或 $HOME 中查找nohup.out。语法是:
1 | nohup 命令 |
如: 后台运行Nmap 端口扫描
1 | $ nohup sudo nmap -sS --top-ports=15 192.168.150.1/24 |
Q: Within zsh the command
&!is a shortcut for disown, i.e. the program won’t get killed upon exiting the invoking shell.
程序退出不被杀死, 同时如果再次登录shell, 那么使用 ps -ef | grep "clash" 会显示clash 的父进程是 1, 这是因为创建的父进程被杀死, 那么由该进程创建的子进程会在父进程被杀死后, 父进程改为 1
1 | nohup clash &> /dev/null &! |
程序退出会被杀死的进程
1 | nohup clash &> /dev/null & |
ctrl+z 以暂停这条命令并返回客户端bg 命令让这条shell命令在后台执行disown -h 这条命令保证当终端关闭时,Shell脚本不会被杀死ctrl+z将任务暂停,这时屏幕会打印这样一句 [1]+ Stopped ./myserver
使用 bg %1 将这个任务转入后台运行(1 是刚才暂停任务的job号,步骤1中屏幕提示方括号里面的内容,如果忘了也可以用 jobs 命令查看)
使用 disown –h %1 指定shell退出时不要发送SIGHUP给任务1(注意任务号前面有个%)
jobs 查看后台执行的命令fg %<d> 将 jobs[d] 切换到前台使用 disown 将 Linux 命令设置为后台运行
Linux 中 的disown命令可以轻松地在后台运行命令。首先,您需要使用& 运算符在后台发送任务。然后,键入disown以将其与shell分离。
1 | $ gedit & disown |
disown 的一个主要优点是,与 nohup 一样,当您关闭 shell 或注销时,系统不会终止您的任务。
使用 Tmux 在后台运行 Linux 命令
Tmux 是一个强大的多路复用器,它允许我们在单个窗口中运行多个终端会话。对于不熟悉它的人来说,学习 tmux 是一个很好的选择。Tmux 使在 Linux 中运行后台命令毫不费力。
1 | tmux new -d 'ping -c 10 8.8.8.8 > www.itpro.net.cn.log' |
当您运行上述tmux命令时,它将在单独的 shell 中执行ping命令并将其保留在后台。您可以使用此方法在后台执行任何 Linux 命令。
能够在后台运行命令使系统管理对管理员来说更有效率。您可以通过多种方式为您的任务做背景。像&和Ctrl + Z这样的 Bash 功能很方便,但系统会在 shell 关闭时终止后台作业。另一方面,即使您注销或终止 shell ,诸如nohup和disown 之类的工具也会使您的命令保持运行。
如果您将程序长时间留在后台,如果编码不当,它们可能会变成僵尸进程。这些过程会显着降低系统速度。因此,请确保每隔一段时间识别并杀死僵尸进程。
-n 是显示行号;相当于nl命令;例子如下:
tail -100f test.log 实时监控100行日志
tail -n 10 test.log 查询日志尾部最后10行的日志;
tail -n +10 test.log 查询10行之后的所有日志;
跟tail是相反的,tail是看后多少行日志;例子如下:
head -n 10 test.log 查询日志文件中的头10行日志;
head -n -10 test.log 查询日志文件除了最后10行的其他所有日志;
tac是倒序查看,是cat单词反写;例子如下:
cat -n test.log |grep "debug" 查询关键字的日志
按行号查看—过滤出关键字附近的日志
cat -n test.log |grep "debug" 得到关键日志的行号cat -n test.log |tail -n +92|head -n 20 选择关键字所在的中间一行. 然后查看这个关键字前10行和后10行的日志:tail -n +92 表示查询92行之后的日志head -n 20 则表示在前面的查询结果里再查前20条记录根据日期查询日志
sed -n '/2014-12-17 16:17:20/,/2014-12-17 16:17:36/p' test.log特别说明:上面的两个日期必须是日志中打印出来的日志,否则无效;
先 grep '2014-12-17 16:17:20' test.log 来确定日志中是否有该 时间点
日志内容特别多,打印在屏幕上不方便查看
使用more和less命令,
如: cat -n test.log |grep "debug" |more 这样就分页打印了,通过点击空格键翻页
使用 >xxx.txt 将其保存到文件中,到时可以拉下这个文件分析
如:cat -n test.log |grep "debug" >debug.txt
| 颜色 | 文件类型标识 | 含义 |
|---|---|---|
| 浅蓝色 | [l] |
软链接 |
| 白色 | [-] |
普通文件/硬链接 |
| 蓝色 | [d] |
目录 |
| 红色 | [l] |
某文件被删除后,链接到该文件的软链接的颜色 |
| 红色闪烁 | 无 | 表示该文件已被删除,但还存在该文件的软链接 |
| 红色 | [-] |
压缩包 |
| 黄色 | [c] |
字符设备文件,如键盘鼠标 |
| 绿色 | [-] |
可执行文件,可执行的程序 |
| 桃红色 | [s] |
套接字文件 |
| 土黄色 | [p] |
管道文件 |
| 灰色文件 | 无 | 表示其它文件 |
1 | netstat -tunlp |
用于显示 tcp,udp 的端口和进程等相关情况。
netstat 查看端口占用语法格式:
1 | netstat -tunlp | grep 端口号 |
例如查看 8000 端口的情况,使用以下命令:
1 | # netstat -tunlp | grep 8000 |
更多命令:
1 | netstat -ntlp //查看当前所有tcp端口 |
不建议使用: 因为每次启动shell都会启动, 而像clash这样的程序只需要执行一次即可, 并且如果多个用户执行, 会导致后执行clash的shell执行失败.
打开 ~/.bashrc 或 ./.zshrc
在文件末尾添加又在每次用户登录时执行的命令, 如
1 | nohup clash &> /dev/null & |
source ~/.bashrc 或 source ~/.zshrc以使更改生效
(Startup Applications Preferences)
~/.config/autostart 目录中的 .desktop 文件中. 每个启动的应用程序都有一个相应的 .desktop 文件,该文件包含有关应用程序启动方式的信息。添加文件 vim <startup_program>.desktop, 添加如下配置
1 | [Desktop Entry] |
如:
1 | Desktop Entry] |
Exec行指定了要在启动时运行的程序的路径
采用systemctl的方式
强烈建议看看这个文档,里面介绍了一些参数的具体含义.
准备工作
写好脚本(假设脚本名为 exec_clash.sh)或将程序放入本地可以运行, 这里以clash为例, 其中, clash位于 /usr/local/bin/clash
exec_clash.sh 中的 exec_clash 是可根据具体脚本的功能改为相应的名字
若是脚本, 则赋予脚本运行权限: chmod +x exec_clash.sh
配置
编写一个 <clash>.service, 内容如下:
1 | [Unit] |
说明
User: 表示以哪个用户启动,不写默认是root. 而用户名也决定了程序启动去哪里读取相应的配置
ExecStart: 就是要执行的语句。
由于为脚本赋予了x(可执行)权限, 因此, ExecStart后面的内容可以直接写成脚本的绝对路径
Restart=always: 只要不是通过systemctl stop来停止服务,任何情况下都必须要重启服务,默认值为no。还可以配置有条件重启,具体参考上述文档。
RestartSec=5: 重启间隔,比如某次异常后,等待5(s)再进行启动,默认值0.1(s)
StartLimitInterval: 无限次重启,默认是10秒内如果重启超过5次则不再重启,设置为0表示不限次数重启
拷贝 clash.service 到 /lib/systemd/system, 需要注意权限问题
修改配置文件后需要重加载配置: sudo systemctl daemon-reload
设置开机自启: sudo systemctl enable clash.service
之后就可以直接重启了
相关操作
| 操作 | 命令 |
|---|---|
| 启动 | sudo systemctl start clash.service |
| 停止 | sudo systemctl stop clash.service |
| 查看状态 | sudo systemctl status clash.service |
测试自动重启功能

大多数默认应用程序将具有位于/usr/share/applications中的.desktop文件。
要了解在启动其中一个应用程序时将运行的相应终端命令,请打开文件浏览器Nautilus,然后右键单击应用程序的图标以在上下文菜单中选择“属性”。这将为您提供所需的所有详细信息(此处显示将运行gnome-control-center -overview的系统设置)
如果通过存储库或dpkg安装了应用程序,则可以使用以下命令:
1 | dpkg -l | grep "<application name>" |
这将搜索所有已安装的应用程序,并搜索其描述;搜索描述是这里的重要部分,因为描述通常包含应用程序的名称,即使运行它的”command”不包含应用程序名称。
例:
在GNOME中,有一个名为Disk Usage Analyzer的应用程序。但是,从终端运行它的命令不是disk-usage-analyzer。要查找其命令,您可以运行:
1 | dpkg -l | grep "disk usage" |
输出应包含此条目:
1 | alaa@aa-lu:~$ dpkg -l | grep "disk usage" |
看第二栏。实际运行磁盘使用率分析器的命令是baobab。
参考目录
1 | mv * .[^.]* <destination_directory> |
解释
1、mv命令的最后一个参数是要移动文件的目标位置;
2、第一个 * 匹配除了隐藏文件的所有文件;
3、隐藏文件使用 .[^.]* 匹配
4、匹配隐藏文件用 .[^.]* 为什么不用 .*: .* 会匹配目录 . 和 ..
5、.[^.]* 的意思是:以 . 开头,加不是 . 的一个任意字符,再加其他任意字符
参数补充
参数说明:
| 参数 | 说明 |
|---|---|
-b |
当目标文件或目录存在时,在执行覆盖前,会为其创建一个备份。 |
-i |
如果指定移动的源目录或文件与目标的目录或文件同名,则会先询问是否覆盖旧文件,输入 y 表示直接覆盖,输入 n 表示取消该操作。 |
-f |
如果指定移动的源目录或文件与目标的目录或文件同名,不会询问,直接覆盖旧文件。 |
-n |
不要覆盖任何已存在的文件或目录。 |
-u |
当源文件比目标文件新或者目标文件不存在时,才执行移动操作。 |
首先,阅读Clash的API的文档RESTful - Proxies
GET 获取所有代理
1 | /proxies |
PUT 切换 Selector 中选中的代理
1 | /proxies/<name> |
(这边的name可以为节点名称,也可以为Selector。只要在proxies/后直接加上字符串就可以,不需要引号或者:)
当前接口只支持切换 Selector 中的代理
| Path Parameters | Body Parameters |
|---|---|
name |
name |
string |
string |
| 代理Selector名称(大小写敏感) | 要切换的代理名字 |
接着,curl使用指南 curl 的用法指南
最后,使用curl切换节点, 使用/proxies获取所有可用代理,然后curl发送PUT请求解决战斗。
1 | curl -X PUT -H "Content-Type: application/json" -d '{"name":"节点名"}' http://localhost:port/proxies/<name> |
-H 添加 HTTP 请求的标头Content-Type: application/json,根据链接2,不设置标头为application/json可能会有问题。
-d 参数用于发送 POST 请求的数据体。
最后的网址为clash的external-controller的网址端口,最后Selector为要选择的proxy-groups的名称。
实际指令类似下面这条:
获取命令
1 | curl -X GET -H "Content-Type: application/json" http://127.0.0.1:9090/proxies -o get.json |
更改节点
1 | curl -X PUT -H "Content-Type: application/json" -d '{"name":"香港-08"}' http://127.0.0.1:9090/proxies/国外流量 -o put.json |
所有命令
1 | curl -X PUT -H "Content-Type: application/json" -d '{"name":"香港-08"}' http://127.0.0.1:9090/proxies/国外流量 -o put.json |
reference: sakronos
通俗解释:镜像就类似操作系统光盘介质,容器相当于通过光盘安装后的系统。
通过光盘(镜像),我们能在不同机器上部署系统(容器),系统内的操作只会保留在当前的系统(容器)中,
如果要升级系统,需要使用到光盘(镜像),但是可能会导致操作系统(容器)的数据丢失。
docker stats: 查看 docker 运行情况
docker images: 查看所有镜像
docker ps 查看当前正在运行的容器,
-a 查看所有已经创建的容器docker inspect <container_name>: 分析容器的详细信息
包含容器所有的配置信息
docker top: 显示指定容器的运行进程
docker logs: 查看容器运行日志
后面加上 container_name 或 ID
OPTIONS:
-f: 类似于 tail -f
docker exec
进入容器: docker exec -it <container_name> <bash或/bin/bash>, 类似于 terminal
docker run
1 | docker run --name <container_name> -d -p 9090:80 images:<TAG > |
-v <宿主机路径>:<容器路径>: 挂载. 可用于防止容器删除后导致数据丢失.del
在我们构建镜像过程中, 可能会产生 none 的镜像, 这些镜像是没有意义的, 只会白白占用系统资源.
构建一个 docker 镜像是基于另一个 docker 镜像构建的.
1 | FROM java:8 |
FROM XXX: 基于了另外一个镜像
ADD:
与构建上下文路径(context_directory)相匹配
docker compose 负责处理容器间的依赖关系, 用于开发测试
docker swarm 支持集群
docker stack 类似于 docker compose, 用于生产环境
用于解决容器之间的依赖关系
docker-compose (单机版)
version: 对版本进行解析
services: 声明 services, 每个 services 对应一个或多个容器. 可以声明多个 services, 可以配置多容器之间的关联.
名字可以随便写
image: 声明容器的镜像, 从哪里下载
restart: 重启策略
ports: 端口映射
links: 声明与其他容器间的关联, 自动解析依赖配置, 解耦绑定关系 depends_on: 通过这个字段容器依赖关系, 需要启动 depends_on 的依赖服务后才能够启动当前 services, 但是不是那么可靠. 需重点关注启动顺序, 关注 “ready”(可以对外提供服务了) 和 “running” 状态, 通过 wait-for-it.sh 用来等待服务可用, 才启动当前 services.
volumes: 数据挂载
可以引用系统里面的环境变量, 包括数据卷、日志卷.
command: 启动命令
deploy(version2): 声明容器对资源的使用限制
Warning
在 version3 中使用 --compatibility
envirionment: 声明环境变量
Shell调试的方法
Makefile学习记录
Secure Shell (SSH) 是一个网络协议,它主要被用来加密客户端和服务端之间的连接。 在客户端和服务端的每一次交互都被加密。
这个教程解释了如何在 Ubuntu 机器上启用 SSH。
启用 SSH 将会允许你远程连接到你的系统,并且执行管理员任务。你将能够通过 scp和sftp安全传输文件。
默认情况下,当 Ubuntu 最初被安装的时候,通过 SSH 进行远程访问是不被允许的。在 Ubuntu 上启用 SSH 非常的简单直接。
以 root 或者其他 sudo 用户身份执行下面的步骤,在你的 Ubuntu 系统上安装并且启用 SSH。
01.使用Ctrl+Alt+T打开终端,并且安装openssh-server软件包:
1 | sudo apt update |
当被提示时,输入你的密码并且按 Enter,继续安装。
02.一旦安装完成,SSH 服务将会被自动启动。你可以验证 SSH 是否正在运行,输入:
1 | sudo systemctl status ssh |
输出将会告诉你服务正在运行,并且启用开机启动:
1 | ● ssh.service - OpenBSD Secure Shell server |
按q返回命令行。
03.Ubuntu 自带一个配置防火墙配置工具,称为 UFW。如果防火墙在你的系统上被启用,请确保打开了 SSH 端口:
1 | sudo ufw allow ssh |
就这些。现在你可以从任何远程机器上通过 SSH 连接到你的 Ubuntu 系统。Linux 和 macOS 系统默认都安装了 SSH 客户端。想要从一个 Window 机器上连接,使用一个 SSH 客户端,例如:PUTTY。
想要在局域网内连接到你的 Ubuntu 机器,以下面的格式输入 ssh 命令,加上用户名和 IP 地址。
1 | ssh username@ip_address |
1 | 确保你将`username`修改成你自己的用户名,`ip_address`修改成你安装了 SSH 的 Ubuntu 机器的 IP 地址。 |
如果你不知道你的 IP 地址,你可以使用ip命令轻易地找到它:
1 | ip a |
你可以从输出中看到,系统 IP 地址是10.0.2.15。
一旦你找到 IP 地址,通过运行下面的ssh 命令登录远程机器:
1 | ssh linuxize@10.0.2.15 |
当你第一次连接时,你将看到下面的信息:
1 | The authenticity of host '10.0.2.15 (10.0.2.15)' can't be established. |
输入yes并且你将会被提示输入你的密码:
1 | Warning: Permanently added '10.0.2.15' (ECDSA) to the list of known hosts. |
一旦你输入密码,你将会看到默认的 Ubuntu 消息:
1 | Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.4.0-26-generic x86_64) |
现在你可以登录到你的 Ubuntu 机器上。
想要通过互联网连接到你的 Ubuntu 机器,你需要知道你的公网 IP 地址,并且配置你的路由器接收端口22的数据,并且发送它到正在运行 SSH 的 Ubuntu 机器。
想要获取你尝试通过 SSH 连接的机器的公网 IP,在这个机器上访问 URL 地址:https://api.ipify.org。
当设置端口转发时,每一个路由器都有不同的方式来设置端口转发。你应该参考你的路由器文档,关于如何设置端口转发的信息。简单来讲,你应该输入之前设置的 SSH 端口号 22,以及之前服务器的私有 IP 地址。
一旦你找到 IP 地址,配置你的路由器,输入:
1 | ssh username@public_ip_address |
如果你将你的机器暴露在互联网中,你最好采取一些安全措施。最基础的一个措施就是配置你的路由器接受一个非标准端口的 SSH 流量,并且转发到你运行 SSH 服务的机器的端口22。
你也可以设置 SSH 密钥公钥验证方式,之后你就可以不使用密码就可以连接到你的 Ubuntu 机器上了。
想要在你的 Ubuntu 系统上禁用 SSH 服务器,简单停止 SSH 即可,输入:
1 | sudo systemctl disable --now ssh |
稍后,你可以重新启用,输入:
1 | sudo systemctl enable --now ssh |
本地客户端生成公私钥:(一路回车默认即可)
1 | ssh-keygen |
生成之后会在用户的根目录生成一个 “.ssh“的文件夹,进入”.ssh“会生成如下几个文件:
1 | cd ~/.ssh |
下创建两个密钥:
authorized_keys: 存放远程免密登录的公钥,主要通过这个文件记录多台机器的公钥id_rsa : 生成的私钥文件id_rsa.pub : 生成的公钥文件know_hosts : 已知的主机公钥清单ps
ssh公钥生效需满足至少下面两个条件:
.ssh 目录的权限必须是700.ssh/authorized_keys 文件权限必须是600这里测试用的服务器地址为:192.168.32.32
用户为:root
1 | ssh-copy-id -i ~/.ssh/id_rsa.pub <remote_ip> |
上面这条命令是写到服务器上的ssh目录下去了
1 | cd ~/.ssh |
可以看到客户端写入到服务器的 id_rsa.pub (公钥)内容。
使用命令ssh-copy-id <remote_ip>
1 | ssh-copy-id root@192.168.32.32 |
通过scp将内容写到对方的authorized_keys文件中
1 | scp -p ~/.ssh/id_rsa.pub root@<remote_ip>:/root/.ssh/authorized_keys |
客户端通过ssh连接远程服务器,就可以免密登录了。
1 | ssh root@192.168.32.32 |
编辑SSH服务器配置文件(通常位于/etc/ssh/sshd_config)。
1 | $ sudo vim /etc/ssh/sshd_config |
确保以下行未注释(没有#):
1 | PubkeyAuthentication yes |
这允许使用SSH密钥和密码进行身份验证。
Match块在配置文件的末尾添加Match块,限制只允许用户user1, user2, user3使用密钥登录。您可以这样做:
1 | Match User user1,user2,user3 |
或
1 | Match User user1 |
这将禁用user1,user2,user3用户的密码登录。其他用户将按照默认设置进行身份验证,允许密码登录。
多个用户也可以单独
保存配置文件并重启SSH服务,以使更改生效:
1 | $ sudo systemctl restart ssh |
现在,user1,user2,user3用户只能使用其SSH密钥进行身份验证,而其他用户仍然可以使用密码进行身份验证。
请确保在执行此操作之前,您已经测试过SSH密钥对的工作方式,并且确保您至少有一种方法可以访问服务器,以防止自身被锁定。
在 ~/.ssh/config 中设置如下内容:
1 | Host * |
它的作用是为所有主机(Host * 表示匹配所有主机)设置一个环境变量 TERM, 其值为 xterm-256color, 具体含义如下:
Host *: 这是一个通配符规则,表示该配置适用于所有远程主机。 如果你只想针对特定主机应用配置,可以将 * 替换为主机名或 IP 地址。SetEnv TERM=xterm-256color : SetEnv 是 SSH 配置指令, 用于在连接到远程主机时传递环境变量. TERM=xterm-256color 设置了终端类型为 xterm-256color, 这是一种支持 256 色的终端模拟器类型。这对于需要彩色输出的程序(如 vim、tmux 等)非常重要。TERM=xterm-256color 的是为了解决远程连接用户时, 键入字符错乱的问题
ssh 连接时, 使用命令行 -o 选项设置环境变量: ssh -o SetEnv="TERM=xterm-256color" user@hostname
格式:scp 要上传的文件路径 用户名@服务器地址:服务器保存路径
例如:把本机 /home/test.txt 文件上传到 192.168.0.101 这台服务器上的 /data/ 目录中
1 | scp /home/test.txt root@192.168.0.101:/data/ |
格式:scp -r 要上传的目录 用户名@服务器地址:服务器的保存目录
例如:把 /home 目录上传到服务器的 /data/ 目录
1 | scp -r /home root@192.168.0.101:/data/ |
格式:scp 用户名@服务器地址:要下载的文件路径 保存文件的文件夹路径
例如:把 192.168.0.101 上的 /data/test.txt 的文件下载到 /home(本地目录)
1 | scp root@192.168.0.101:/data/test.txt /home |
格式:scp -r 用户名@服务器地址:要下载的服务器目录 保存下载的目录
例如:把 192.168.0.101 上的 /data 目录下载到 /home(本地目录)
1 | scp -r root@192.168.0.101:/data /home/ |
注:目标服务器要开启写入权限。
假设目标服务器的 ip 地址为: 192.168.32.32, port 为 8080
则在本地的浏览器地址栏输入: <ip>:<port>, 这里输入: 192.168.32.32:8080
将服务器 port 端口转发到本地 port
如将服务器 9090 端口转发到本地 19090.
1 | ssh -L 19090:127.0.0.1:9090 <ip>:<port> |
地址栏输入: localhost:19090 或 127.0.0.1:19090
1 | Match User <用户名> |
很多环境都有一台统一登录跳板机,我们需要先登录跳板机,然后再登录自己的目标机器.登录流程如下
1 | ssh root@跳板机ip |
首先通过 ssh 连接跳板机, 并设置端口转发, 如将本地端口 2222 通过跳板机 (132.60.171.142) 转发到服务器 (192.168.2.28) 的 22 端口, ~/.ssh/config 配置如下:
1 | Host linkrm |
在本地运行命令: ssh linkrm
通过 ssh 连接本地端口 2222, 访问服务器 (192.168.2.28), ~/.ssh/config 配置如下:
1 | Host rm |
在本地另开一个终端, 并运行命令 ssh rm
有一台机器A,欲与机器C建立SSH连接,但由于隔离限制(比如”存在防火墙”)该SSH连接不能直接建立。ssh命令的”ProxyCommand”选项被设计用来解决以上问题。
通过“ProxyCommand”选项,机器A能够灵活使用任意代理机制与机器C上的SSH Server端口建立连接,接着机器A上的SSH Client再与该连接进行数据交互,从而机器A上的SSH Client与机器C上的SSH Server之间建立了与一般“直接SSH连接”不太一样的“间接SSH连接”。不过由于“间接SSH连接”的透明性,逻辑上可认为机器A上的SSH Client与机器C上的SSH Server建立了“直接SSH连接”。
“直接SSH连接”示意图如图1,“间接SSH连接”示意图如图2。
常使用代理机制有两种,接下来进行介绍。
ProxyCommand 选项值形式为 ssh -W C:CPort -l USER -i PRIVATE_KEY -p BPort B. 原理是 ssh 命令自提供的代理机制,在机器A上另外单独建立与B的SSH连接(使用-l USER -i PRIVATE_KEY -p BPort B这些参数),该SSH连接的B端侧与机器C上的SSH Server端口(即C:CPort)建立连接,该SSH连接的A端侧与机器A上的SSH Client(即w最终欲建立’间接SSH连接’在机器A上的SSH Client”)建立连接。
假定A上ssh_config配置文件内容如下:
1 | Host B |
在A上执行 ssh C 命令,发现A与C成功建立SSH连接。根据以上所述,此时在A上应该有两个SSH进程,一个对应于”A与B的SSH连接”,另外一个对应于”A与C的SSH连接”。在A上执行 ps -ef | grep 'ssh' 命令,得到如下结果,得证。
1 | 7851 7689 15:55 S pts/10 ssh C |
“ProxyCommand”选项值形式为”nc -X 5 -x B:BPort C CPort”,原理:利用”nc”命令,在机器A上使用”nc”命令与代理服务器(即”-x B:BPort”,通过”-X 5”参数来指定与代理服务器的通信协议为”SOCKS4/SOCKS5/HTTPS”)建立代理连接,该代理连接的B端侧与机器C上的SSH Server端口(即”C CPort”)建立连接,该代理连接的A端侧与机器A上的SSH Client(即”最终欲建立‘间接SSH连接’在机器A上的SSH Client”)建立连接。
假定A上ssh_config配置文件内容如下:
1 | Host C |
在A上执行ssh C命令,发现A与C成功建立SSH连接。根据以上所述,此时在A上应该有一个NC进程和一个SSH进程,前者对应于”A与B的代理连接”,后者对应于”A与C的SSH连接”。在A上执行ps -ef | grep -e 'ssh' -e 'nc'命令,得到如下结果,得证。
1 | $ ps -ef | grep -e 'ssh' -e 'nc' |
端口转发包括:本地转发,远端转发和动态转发。其中”本地转发”和”远端转发”属于”静态转发”(因为转发目标端口是固定的)。
ProxyCommand选项能够使用基于”动态转发”的代理机制(在外面封装nc命令层),而不能使用基于”静态转发”的代理机制。
参考文献:
| 参数 | 功能描述 | 示例 |
|---|---|---|
-L [本地端口]:[目标主机]:[目标端口] |
设置本地端口转发。 | ssh -L 8080:localhost:80 user@remote_host |
-R [远程端口]:[目标主机]:[目标端口] |
设置远程端口转发。 | ssh -R 9090:localhost:3000 user@remote_host |
-D [本地端口] |
设置动态端口转发,创建SOCKS代理。 | ssh -D 1080 user@remote_host |
-p [端口号] |
指定SSH连接的端口(默认为22)。 | ssh -p 2222 user@remote_host |
-v |
启用详细模式,显示调试信息,便于排查连接问题。 | ssh -v user@remote_host |
-C |
启用压缩,适用于慢速连接,可能会提高性能。 | ssh -C user@remote_host |
-i [私钥文件] |
指定使用的SSH私钥文件。 | ssh -i ~/.ssh/id_rsa user@remote_host |
-N |
不执行远程命令,仅用于端口转发。 | ssh -L 8080:localhost:80 -N user@remote_host |
-f |
在后台运行SSH会话,通常与-N结合使用。 | ssh -f -L 8080:localhost:80 -N user@remote_host |
-o [选项] |
可以传递特定的SSH配置选项。 | ssh -o StrictHostKeyChecking=no user@remote_host |
找到.ssh目录,下面有一个known_hosts文件,删除 ~/.ssh/known_hosts 文件,或者如果你可以判断出known_hosts中原ssh服务器的公钥,删去那部分,
见这里, 设置远程机器的 TERM 变量为 xterm-256color.
新建.txt 文件
1 | Windows Registry Editor Version 5.00 |
win+r 输入 regedit, 打开注册表
找到 计算机\HKEY_CURRENT_USER\Software\Microsoft\InputMethod\Settings\CHS 项
新建字符串值
名为 UserDefinedDoublePinyinScheme0
值为 小鹤双拼*2*^*iuvdjhcwfg^xmlnpbksqszxkrltvyovt
1 | Windows Registry Editor Version 5.00 |
1 | Windows Registry Editor Version 5.00 |
1 | Windows Registry Editor Version 5.00 |
在 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout\ 下新建一个二进制值项: “Scancode Map”
Scancode Map格式说明
1 | 00 00 00 00 00 00 00 00 (固定格式) |
1 | 0000 00 00 00 00 00 00 00 00 |
00 00 00 00 00 00 00 00: 固定,照写就好
04 00 00 00: 04代表要修改三个按键的映射,两个是02,一个是01
1D 00 5B E0
1D 00 : 是修改后的按键扫描码;5B E0 : 是原按键的扫描码; 左Ctrl键的扫描码是1D 00,左win键的扫描码5B E0;
这四个字节代表的意思是将 左win键 替换成 左Ctrl键。
1D E0 5C E0: 这四个字节代表的意思是将 右win键 替换成 右Ctrl键。
5B E0 1D 00: 这四个字节代表的意思是将 左Ctrl键 替换成 左win键。
00 00 00 00: 固定,照写就好
添加 box 到vagrant
1 | vagrant box add <box_name> <box_path> |
<box_name>: 为 box 取的名字,供后续创建虚拟机<box_path>: vagrantbox 所在地址查看是否添加成功
1 | vagrant box list |
创建虚拟机
创建一个目录, 然后在该目录下执行以下命令
1 | vagrant init <box_name> |
会生成一个 Vagrantfile 文件
修改 Vagrantfile 文件配置
启动虚拟机
1 | vagrant up |
进入虚拟机
1 | vagrant ssh |
vagrantvagrant关闭虚拟机
1 | vagrant halt |
PS:
- 每次启动虚拟机需要使用命令行(cmd)进入
Vagrantfile文件所在的目录,然后再使用vagrant up命令启动虚拟机。- 关机同理,使用命令行(cmd)进入
Vagrantfile文件所在的目录,然后再使用vagrant halt命令关闭虚拟机。
双击打开, 默认安装就可以
fate 官方测试使用的版本为 18.04
win10系统下的设置
进入windows 防火墙
控制面板 -> 系统和安全 -> Windows Defender 防火墙 -> 高级设置 -> 入站规则 -> 文件和打印机共享(回显请求 - ICMPv4-In)
右键属性 -> 作用域 -> 远程IP地址 -> 下列 IP 地址 -> 添加
我使用的是 此IP地址或子网
目的: 仅允许部分用户使用
如添加: 10.132.0.0/16
测试是否成功配置
即查看是否可以 ping 通
查看名称或接口索引
方法1
1 | netsh interface ipv4 show interfaces |
我的这里是 WLAN
方法2 (未测试)
设置 config.vm.network "public_network", bright: "WLAN"
bright: 就是上面的“查看名称或接口索引 ”
启动/重启虚拟机: vagrant up/vagrant reload
查看ip地址: ifconfig, 我这里的是 10.132.22.5
设置Vigrant: config.vm.network "public_network", bright: "WLAN", ip: "10.132.22.5"
也可以加入子网掩码, 如
config.vm.network "public_network", bridge: "WLAN", netmask: "255.255.0.0", ip: "10.132.22.5"
passwd
或
1 | sudo su |
PS: 更多信息查看 Ubuntu 处理方法
进入 BIOS 下, 开启 Intel (VMX) 虚拟化技术
找到.ssh目录,下面有一个known_hosts文件,删除 ~/.ssh/known_hosts 文件,或者如果你可以判断出known_hosts中原ssh服务器的公钥,删去那部分,
进入虚拟机
方法1: vagrant ssh
在 Vagrantfile 文件夹下打开 cmd, 运行命令 vagrant ssh
方法2: virtualbox
打开 Vagrantfile 中的 virtualbox 显示
1 | config.vm.provider "virtualbox" do |vb| |
然后运行 vagrant up (虚拟机处于关机状态) 或 vagrant reload (虚拟机处于开机状态)
更改 /etc/ssh/sshd_config 文件下的 PasswordAuthentication no 改为 PasswordAuthentication yes
重启虚拟机 vagrant reload
那就把局域网扩大一些, 可以通过设置子网掩码来设置
临时设置子网掩码
查看网络: ifconfig
1 | enp0s3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 |
设置子网
1 | sudo ifconfig enp0s8 10.10.32.32 netmask 255.255.0.0 |
在先前的章节中,您学习了如何使用Vim。在本章,您将学习如何组织和配置Vimrc。
对于Vimrc,常见的理解是在根目录下添加一个 .vimrc 点文件(根据您使用的操作系统,文件路径名可能不同)。
实际上,Vim在多个地方查找vimrc文件。下面是Vim检查的路径:
$VIMINIT$HOME/.vimrc$HOME/.vim/vimrc$EXINIT$HOME/.exrc$VIMRUNTIME/default.vim当您启动Vim时,它将在上面列出的6个位置按顺序检查vimrc文件,第一个被找到的vimrc文件将被加载,而其余的将被忽略。
首先,Vim将查找环境变量 $VIMINIT。如果没有找到,Vim将检查 $HOME/.vimrc。如果还没找到,VIm就检查 $HOME/.vim/vimrc。如果Vim找到了vimrc文件,它就停止查找,并使用 $HOME/.vim/vimrc。
关于第一个位置,$VIMINIT 是一个环境变量。默认情况下它是未定义的。如果您想将 ~/dotfiles/testvimrc 作为 $VIMINTI 的值,您可以创建一个包含那个vimrc路径的环境变量。当您运行 export VIMINIT='let $MYVIMRC="$HOME/dotfiles/testvimrc" | source $MYVIMRC'后,VIm将使用 ~/dotfiles/testvimrc 作为您的vimrc文件。
第二个位置,$HOME/.vimrc 是很多Vim用户习惯使用的路径。$HOME 大部分情况下是您的根目录(~)。如果您有一个 ~/.vimrc 文件,Vim将使用它作为您的vimrc文件。
第三个,$HOME/.vim/vimrc,位于 ~/.vim 目录中。您可能已经有了一个 ~/.vim 目录用于存放插件、自定义脚本、或视图文件。注意这里的vimrc文件名没有“点”($HOME/.vim/.vimrc 不会被识别,但 $HOME/.vim/vimrc能被识别)。
第四个,$EXINIT 工作方式与 $VIMINIT 类似。
第五个,$HOME/.exrc 工作方式与 $HOME/.vimrc 类似。
第六个,$VIMRUNTIME/defaults.vim 是Vim编译时自带的默认vimrc文件。在我的电脑中,我是使用Homebrew安装的Vim8.2,所以我的路径是(/usr/local/share/vim/vim82)。如果Vim在前5个位置都没有找到vimrc文件,它将使用这个Vim自带的vimrc文件。
在本章剩余部分,我将假设vimrc使用的路径是 ~/.vimrc。
我刚开始配置Vimrc时,曾问过一个问题,“我究竟该把什么放在Vimrc文件中?”。
答案是,“任何您想放的东西”。 直接复制粘贴别人的vimrc文件的确是一个诱惑,但您应当抵制这个诱惑。如果您仍然坚持使用别人的vimrc文件,确保您知道这个vimrc干了什么,为什么他/她要用这些设置?以及他/她如何使用这些设置?还有最重要的是,这个vimrc文件是否符合你的实际需要?别人使用并不代表您也要使用。
简单地说,一个vimrc是以下内容的集合:
当然还有一些上面没有提到的内容,但总体说,已经涵盖了绝大部分使用场景。
在前面的章节中,我曾提到很多不同的插件,比如fzf.vim, vim-mundo, 还有 vim-fugitive.
十年前,管理插件插件是一个噩梦。但随着很多现代插件管理器的开发,现在安装插件可以在几秒内完成。我现在正在使用vim-plug作为我的插件管理器,所以我在本节中将使用它。相关概念和其他流行的插件管理器应该是类似的。我强烈建议您多试试几个插件管理器,比如:
除了上面列出的,还有很多插件管理器,可以随便看看。要想安装 vim-plug,如果您使用的是Unix,运行:
1 | curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim |
要添加新的插件,将您的插件名(比如,Plug 'github-username/repository-name') 放置在 call plug#begin() 和 call plug#end() 之间的行中. 所以,如果您想安装 emmet-vim 和 nerdtree,将下面的片段放到您的vimrc中:
1 | call plug#begin('~/.vim/plugged') |
然后保存修改,加载当前vimrc (:source %), 然后运行 :PlugInstall 安装插件。
如果以后您想删除不使用的插件,您只需将插件名从 call 代码块之间移除,保存并加载,然后运行 :PlugClean 命令将它从机器上删除。
Vim 8 有自己的内置包管理器。您可以查阅 :h packages 了解更多信息。在后面一章中,我将向您展示如何使用它。
在任意一个vimrc文件中都可以看到大量的 set 选项。 如果您在命令行模式中运行 set 命令,它只是暂时的。当您关闭Vim,设置就会丢失。比如,为了避免您每次运行Vim时都必须在命令行模式运行 :set relativenumber number 命令,您可以将这个命令添加在vimrc中:
1 | set relativenumber number |
有一些设置需要您赋予一个值,比如 set tabstop=2。想了解一个设置可以接收什么类型的值,可以查看帮助页。
您也可以使用 let 来代替 set(==确保在选项前添加一个 &号==)。使用 let ,您可以使用表达式进行赋值。比如,要想仅当某个路径存在时,才将该路径赋予 'dictionary' 选项:
1 | let s:english_dict = "/usr/share/dict/words" |
在后面的章节中您将了解关于Vimscript赋值和条件的知识。
要查看Vim中所有可用的选项,查阅 :h E355。
Vimrc是一个很好的用来放置自定义函数的地方。在后面的章节中您将学习如何写您自己的Vimscript函数。
您可以使用 command 创建一个自定义命令行命令。
比如,创建一个用于显示今天日期的基本命令 GimmeDate:
1 | :command! GimmeDate echo call("strftime", ["%F"]) |
当您运行 :GimmeDate 时,Vim将显示一个类似 “2021-01-1”的日期。
要创建一个可以接收输入的基本命令,您可以使用 <args> 。如果您想向 GimmeDate 传递一个时间/日期格式参数:
1 | :command! GimmeDate echo call("strftime", [<args>]) |
如果您想限定参数的数目,您可以使用 -nargs 标志。-nargs=0 表示没有参数,-nargs=1 表示传递1个参数,-nargs=+ 表示至少1个参数,-nargs=* 表示传递任意数量的参数,-nargs=? 表示传递0个或1个参数。如果您想传递n个参数,使用 -nargs=n(这里 n 是一个任意整数)。
<args> 有两个变体:<f-args> 和 <q-args> 。前者用来向Vimscript函数传递参数,后者用来将用户输入自动转换为字符串。
使用 args:
1 | :command! -nargs=1 Hello echo "Hello " . <args> |
使用 q-args:
1 | :command! -nargs=1 Hello echo "Hello " . <q-args> |
使用 f-args:
1 | :function! PrintHello(person1, person2) |
当您学了关于Vimscript函数的章节后,上面的函数将更有意义。
查阅 :h command 和 :args 了解更多关于command和args的信息。
如果您发现您重复地执行一些相同的复杂操作,那么为这些复杂操作建立一个键盘映射将会很有用:
比如,在我的vimrc文件中有2个键盘映射:
1 | nnoremap <silent> <C-f> :GFiles<CR> |
在第一个中,我将 Ctrl-F 映射到 fzf.vim 插件的 :Gfiles 命令(快速搜索Git文件)上。在第二个中,我将 <leader>tn 映射到调用一个自定义函数 ToggleNumber (切换 norelativenumber 和 relativenumber 选项)。Ctrl-f 映射覆盖了Vim的原生的页面滚动。如果发生冲突,您的映射将会覆盖Vim的设置。因为从几乎从来不用Vim原生的页面滚动功能,所以我认为可以安全地覆盖它。
另外,在 <Leader>tn 中的 “leader” 键到底是什么?
Vim有一个leader键用来辅助键盘映射。比如,我将 <leader>tn 映射为运行 ToggleNumber() 函数。如果没有leader键,我可能会用 tn,但Vim中的 t 已经用做其他功能(”till”搜索导航命令)了。有了leader键,我现在先按定义好的leader键作为开头,然后按 tn,而不用干扰已经存在的命令。您可以设置leader键作为您映射的连续按键的第一个按键。默认Vim使用反斜杠作为leader键(所以 <Leader>tn 会变成 “反斜杠-t-n”)。
我个人喜欢使用空格 <Space> 作为leader键,代替默认的反斜杠。要想改变您的leader键,将下面的文本添加到您的vimrc中:
1 | let mapleader = "\<space>" |
上面的 nnoremap 命令可以分解为三个部分:
n 表示普通模式。nore 表示禁止递归。map 是键盘映射命令。如果不想使用 nnoremap,您至少也得使用 nmap (nmap <silent> <C-f> :Gfiles<CR>)。但是,最好还是使用禁止递归的版本,这样是为了避免键盘映射时潜在的无限循环风险。
如果您进行键盘映射时不使用禁止递归,下面例子演示了会发生什么。假设您想给 B 添加一个键盘映射,用来在一行的末尾添加一个分号,然后跳回前一个词组(回想一下,B 是Vim普通模式的一个导航命令,用来跳回前一个词组)。
1 | nmap B A;<esc>B |
当您按下 B …哦豁,Vim开始失控了,开始无止尽的添加;(用 Ctrl-c终止)。为什么会发生这样的情况?因为在键盘映射 A;<esc>B中,这个 B不再是Vim原生的导航命令,它已经被映射到您刚才创建的键盘映射中了。这是您实际上执行的操作序列:
1 | A;<esc>A;<esc>A;<esc>A;esc>... |
要解决这个问题,您需要指定键盘映射禁止递归:
1 | nnoremap B A;<esc>B |
现在再按一下 B 试试。这一次它成功地在行尾添加了一个 ;,然后跳回到前一个词组。这个映射中的 B 就表示Vim原生的 B了。
Vim针对不同的模式有不同的键盘映射命令。如果您想创建一个插入模式下的键盘映射 jk,用来退出插入模式:
1 | inoremap jk <esc> |
其他模式的键盘映射命令有:map(普通、可视、选择、以及操作符等待模式), vmap(可视、选择), smap(选择), xmap(可视), omap(操作符等待模式), map!(插入、命令行), lmap(插入,命令行,Lang-arg模式), cmap(命令行), 还有tmap(终端任务)。在这里我不会详细的讲解它们,要了解更多信息,查阅 :h map.txt。
创建最直观、最一致、最易于记忆的键盘映射。
一段时候键,您的vimrc文件就会变大且复杂得难以阅读。有两种方法让您的vimrc文件保持整洁:
您可以使用Vim的 :source 命令将您的vimrc文件划分为多个文件。这个命令可以根据给定的文件参数,读取文件中的命令行命令。
让我们在 ~/.vim 下创建一个子文件夹,取名为 /settings(~/.vim/settings)。名字可以取为任意您喜欢的名字。
然后你在这个文件夹下创建4个文件:
~/.vim/settings/plugins.vim).~/.vim/settings/configs.vim).~/.vim/settings/functions.vim).~/.vim/settings/mappings.vim) .在 ~/.vimrc 里面添加:
1 | source $HOME/.vim/settings/plugins.vim |
在 ~/.vim/settings/plugins.vim 里面:
1 | call plug#begin('~/.vim/plugged') |
在 ~/.vim/settings/configs.vim 里面:
1 | set nocompatible |
在 ~/.vim/settings/functions.vim 里面:
1 | function! ToggleNumber() |
在 ~/.vim/settings/mappings.vim 里面:
1 | inoremap jk <esc> |
这样您的vimrc文件依然能够正常工作,但现在它只有4行了。
使用这样的设置,您可以轻易知道到哪去修改配置。如果您要添加一些键盘映射,就将它们添加在 /mappings.vim 文件中。以后,当您的vimrc变大时,您总是可以新建几个子文件来缩小它的大小。比如,如果您想为主题配色创建相关设置,您可以添加 ~/.vim/settings/themes.vim。
如果您倾向于保持一个单独的vimrc文件,以使它更加便于携带,您可以使用标志折叠让它保持有序。在vimrc文件的顶部添加一下内容:
1 | " setup folds {{{ |
Vim能够检测当前buffer所属的文件类型 (:set filetype?). 如果发现属于 vim 类型,您可以使用标志折叠。回想一个标志折叠的用法,它使用 {{{ 和 }}} 来指明折叠的开始和结束。
添加 {{{ 和 }}} 标志将您的vimrc文件其他部分折叠起来。(别忘了使用 " 对标志进行注释):
1 | " setup folds {{{ |
您的vimrc文件将会看起来类似下面:
1 | +-- 6 lines: setup folds ----- |
如果您要启动Vim时,既不加载Vimrc,也不加载插件,运行:
1 | vim -u NONE |
如果您要启动Vim时,不加载Vimrc,但加载插件,运行:
1 | vim -u NORC |
如果您要启动Vim时,加载Vimrc,但不加载插件,运行
1 | vim --noplugin |
如果您要Vim启动加载一个 其他的 vimrc, 比如 ~/.vimrc-backup, 运行:
1 | vim -u ~/.vimrc-backup |
Vimrc是定制Vim时的一个重要组件,学习构建您的Vimrc最好是首先阅读他人的vimrc文件,然后逐渐地建立自己的。最好的vimrc并不是谁谁谁使用的,而是最适合您的工作需要和编辑风格的。
在前面的章节中,我提到使用第三方插件管理器来安装插件。从Vim 8开始,Vim自带了一个内置的插件管理器,名叫 软件包(packages)。在本章,您将学习如何使用Vim软件包来安装插件。
要看您的Vim编译版本是否能够使用软件包,运行 :version。然后查看是否有 +packages属性。另外,您也可以运行 :echo has('packages')(如果返回1,表示可以使用软件包)。
在根目录下查看您是否有一个 ~/.vim 文件夹。如果没有就新建一个。在文件夹里面,创建一个子文件夹取名 pack(~/.vim/pack/)。Vim会在这个子文件夹内自动搜索插件。
Vim软件包有两种加载机制:自动加载和手动加载。
要想让Vim启动时自动加载插件,你需要将它们放置在 start/子目录中。路径看起来像这样:
1 | ~/.vim/pack/*/start/ |
现在您可能会问,为什么在pack/ 和 start/ 之间有一个 * ?这个星号可以是任意名字。让我们将它取为packdemo/:
1 | ~/.vim/pack/packdemo/start/ |
记住,如果您忽略这一点,用下面的路径代替的话:
1 | ~/.vim/pack/start/ |
软件包系统是不会正常工作的。 必须在pack/ 和 start/之间添加一个名字才能正常运行。
在这个示例中,让我们尝试安装 [NERDTree](https://github.com/preservim/nThe package system won’t work. It is imperative to put a name between pack/ and start/.erdtree) 插件。用任意方法进入 start/ 目录(cd ~/.vim/pack/packdemo/start/),然后将NERDTree的仓库克隆下来:
1 | git clone https://github.com/preservim/nerdtree.git |
完成了!您已经完成了安装。下一次您启动Vim,您可以立即执行 NERDTree 命令 :NERDTreeToggle。
在 ~/.vim/pack/*/start/ 目录中,您想克隆多少插件仓库就克隆多少。Vim将会自动加载每一个插件。如果您删除了克隆的仓库(rm -rf nerdtree),那么插件就失效了。
要想在Vim启动时手动加载插件,您得将相关插件放置在 opt/ 目录中,类似于自动加载,这个路径看起来像这样:
1 | ~/.vim/pack/*/opt/ |
让我们继续使用前面的 packdemo/ 这个名字:
1 | ~/.vim/pack/packdemo/opt/ |
这一次,让我们安装killersheep 游戏(需要Vim8.2以上版本)。进入opt/ 目录(cd ~/.vim/pack/packdemo/opt/) 然后克隆仓库:
1 | git clone https://github.com/vim/killersheep.git |
启动Vim。执行游戏的命令是 :KillKillKill。试着运行一下。Vim将会提示这不是一个有效的编辑命令。您需要首先 手动 加载插件,运行:
1 | :packadd killersheep |
现在再运行一下 :KillKillKill 。命令已经可以使用了。
您可能好奇,“为什么我需要手动加载插件?启动时自动加载岂不是更好?”
很好的问题。有时候有些插件我们并不是所有的时候都在用,比如 KillerSheep 游戏。您可能不会想要加载10个不同的游戏导致Vim启动变慢。但是偶尔当您觉得乏味的时候,您可能想要玩几个游戏,使用手动加载一些非必须的插件。
您也可以使用这个方法有条件的加载插件。可能您同时使用了Neovim和Vim,有一些插件是为NeoVim优化过的。您可以添加类似下列的内容到您的vimrc中:
1 | if has('nvim') |
回想一下,要使用Vim的软件包系统必须有以下需求:
1 | ~/.vim/pack/*/start/ |
或者:
1 | ~/.vim/pack/*/opt/ |
实际上,*星号可以使 任意 名字,这个名字就可以用来管理您的插件。假设您想将您的插件根据类型(颜色、语法、游戏)分组:
1 | ~/.vim/pack/colors/ |
您仍然可以使用各个目录下的 start/ 和 opt/ 。
1 | ~/.vim/pack/colors/start/ |
您可能好奇,Vim软件包是否让一些流行的插件管理器,比如 vim-pathogen, vundle.vim, dein.vim, a还有vim-plug面临淘汰?
答案永远是:“看情况而定。”
我仍然使用vim-plug,因为使用它添加、删除、更新插件很容易。如果您使用了很多插件,插件管理器的好处更加明显,因为使用它可以对很多插件进行同时更新。有些插件管理器同时也提供了一些异步功能。
如果您是极简主义者,可以尝试一下Vim软件包。如果您是一名插件重度使用者,您可能需要一个插件管理器。
在前面的章节中,我提到Vim会自动查找一些特殊的路径,比如在~/.vim/ 中的 pack/(第23章) compiler/(第19章)。这些都是Vim runtime路径的例子。
除了上面提到的两个,Vim还有更多runtime路径。在本章,您将学习关于Vim runtime路径的高层次概述。本章的目标是向您展示它们什么时候被调用。知道这些知识能够帮您更进一步理解和定制Vim。
在一台Unix机器中,其中一个vim runtime路径就是 $HOME/.vim/ (如果您用的是其他操作系统,比如Windows,您的路径可能有所不同)。要查看不同的操作系统有什么样的runtime路径,查阅 :h runtimepath。在本章,我将使用 ~/.vim/ 作为默认的runtime路径。
Vim有一个runtime路径 plugin,每次Vim启动时都会执行这个路径中的所有脚本。不要把这个名字 “plugin” 和Vim的外部插件(external plugins,比如NERDTree, fzf.vim, 等)搞混了。
进入 ~/.vim/ 目录,然后创建 plugin/ 子目录。 创建两个文件: donut.vim 和 chocolate.vim。
在 ~/.vim/plugin/donut.vim里面:
1 | echo "donut!" |
在 ~/.vim/plugin/chocolate.vim里面:
1 | echo "chocolate!" |
现在关闭Vim。下次您启动Vim,您将会看到 "donut!" 和 :chocolate! 的显示。此 plugin runtime路径可以用来执行一些初始化脚本。
在开始之前,为保证检测能正常运行,确保在您的vimrc中至少包含了下列的行:
1 | filetype plugin indent on |
查阅 :h filetype-overview 了解更多信息。本质上,这条代码开启Vim的文件类型检测。
当您打开一个新的文件,Vim通常知道这个文件是什么类型。如果您有一个文件 hello.rb,运行 :set filetype? 会返回正确的相应 filetype=ruby。
Vim知道如何检测 “常见” 的文件类型(Ruby, Python, Javascript, 等)。但如果是一个自定义文件会怎样呢?您需要告诉Vim去检测它,并给它指派一个正确的文件类型。
有两种检测方法:使用文件名和使用文件内容
文件名检测使用一个文件的文件名来检测文件类型。当您打开 hello.rb文件时,Vim依靠扩展名 .rb 知道它是一个Ruby文件。
有两种方法实现文件名检测:一是使用 ftdetect runtime目录,二是使用 filetype.vim runtime文件。我们两个都看一看。
ftdetect/让我们创建一个古怪(但优雅)的名字,hello.chocodonut。当您打开它后运行 :set filetype? ,因为它的后缀名不是常见的文件名,Vim不知道它是什么类型,会返回 filetype=。
您需要指示Vim将所有以 .chocodonut结尾的文件设置为 “chocodonut”类型的文件。在runtime路径根目录(~/.vim/)创建一个子目录,名为 ftdetect/ 。在子目录里面,再创建一个名叫 chocodonut.vim 的文件(~/.vim/ftdetect/chocodonut.vim),在文件里面,添加:
1 | autocmd BufNewFile,BufRead *.chocodonut set filetype=chocodonut |
当您创建新buffer或打开新buffer时,事件BufNewFile 和 BufRead 就会被触发。 *.chocodonut 意思是只有当新打开的buffer文件名后缀是 .chocodonut 时事件才会被触发。最后,set filetype=chocodonut 命令将文件类型设置为chocodonut类型。
重启Vim。新建一个 hello.chocodonut 文件然后运行 :set filetype?。它将返回 filetype=chocodonut.
好极了!只要您想,您可以将任意多的文件放置在 ftdetect/ 中。以后,如果您想扩展您的 donut 文件类型,你可以添加 ftdetect/strawberrydonut.vim, ftdetect/plaindonut.vim 等等。
在Vim中,实际上有两种方法设置文件类型。其中给一个是您刚刚使用的 set filetype=chocodonut。另一种方法是运行 setfiletype chocodonut。前一个命令 set filetype=chocodonut 将 总是 设置文件类型为chocodonut。 而后者setfiletype chocodonut只有当文件类型尚未设置时,才会将文件类型设置为chocodonut。
第二种文件类型检测需要你创建一个名为 filetype.vim的文件,并将它放置在根目录(~/.vim/filetype.vim)。在文件内添加一下内容:
1 | autocmd BufNewFile,BufRead *.plaindonut set filetype=plaindonut |
创建一个名为 hello.plaindonut 的文件。当你打开它后运行 :set filetype? Vim会显示正确的自定义文件类型 filetype=plaindonut。
太好了,修改生效了。另外,如果您仔细看看 filetype.vim ,您会发现当您打开hello.plaindonut时,这个文件文件运行了多次。为防止这一点,您可以添加一个守卫,让主脚本只运行一次。更新 filetype.vim:
1 | if exists("did_load_filetypes") |
finish 是一个Vim命令,用来停止执行剩余的脚本。表达式"did_load_filetypes" 并 不是 一个Vim内置函数。它实际上是$VIMRUNTIME/filetype.vim 中的一个全局变量。如果您好奇,运行:e $VIMRUNTIME/filetype.vim。您将会发现以下内容:
1 | if exists("did_load_filetypes") |
当Vim调用这个文件时,它会定义 did_load_filetypes 变量,并将它设置为 1 。在Vim中,1 表示真。你可以试着读完 filetype.vim 剩余的内容,看看您是否能够理解当Vim调用它时干了什么。
让我们学习如何基于文件内容检测文件类型。
假设您有一个无扩展名的文件的集合。这些文件唯一相同的地方是,第一行都是以 “donutify” 开头。您现在想给这些文件指派一个 donut 的文件类型。创建新文件,起名为 sugardonut, glazeddonut, 还有 frieddonut(没有扩展名)。在每个文件中,添加下列内容:
1 | donutify |
当您在sugardonut中运行 :set filetype?,Vim无法知道应该给这个文件指派什么文件类型,会返回 filetype=。
在runtime根目录,添加一个 scripts.vim 文件(~/.vim/scripts.vim),在文件中,添加一下内容:
1 | if did_filetype() |
函数 getline(1) 返回文件第一行的内容。它检查第一行是否以 “donutify” 开头。函数 did_filetype() 是Vim的内置函数,当一个与文件类型相关的事件发生至少一次时,它返回真。它用来做守卫,防止文件类型事件反复运行。
打开文件 sugardonut 然后运行 :set filetype?,Vim现在返回 filetype=donut。如果您打开另外一个donut文件 (glazeddonut 和 frieddonut),Vim同样会将它们的文件类型定义为 donut 类型。
注意,scripts.vim 仅当Vim打开一个未知文件类型的文件时才会运行。如果Vim打开一个已知文件类型的文件,scripts.vim 将不会运行。
如果您想让Vim仅当您打开一个 chocodonut 文件时才运行 chocodonut 相关的特殊脚本,而当您打开的是 plaindonut 文件时,Vim就不运行这些脚本。能否做到呢?
您可以使用文件类型插件runtime路径(~/.vim/ftplugin/)来完成这个功能。Vim会在这个目录中查找一个文件,这个文件的文件名与您打开的文件类型一样。创建一个文件,起名为chocodonut.vim (~/.vim/ftplugin/chocodonut.vim):
1 | echo "Calling from chocodonut ftplugin" |
创建另一个 ftplugin 文件,起名为plaindonut.vim (~/.vim/ftplugin/plaindonut.vim):
1 | echo "Calling from plaindonut ftplugin" |
现在,每次您打开一个 chocodonut 类型的文件时,Vim会运行 ~/.vim/ftplugin/chocodonut.vim中的脚本。每次您打开 plaindonut 类型的文件时,Vim会运行 ~/.vim/ftplugin/plaindonut.vim 中的脚本。
一个警告:每当一个buffer的文件类型被设置时(比如,set filetype=chocodonut),上述脚本就会运行一次。如果您打开3个不同的 chocodonut 文件,该脚本将运行 总共 3次。
Vim有一个 缩进runtime路径,其工作方式与ftplugin类似,Vim也会在这个目录中查找一个与打开的文件类型名字一样的文件。缩进runtime路径的目的是存储缩进相关的代码。如果您有文件 ~/.vim/indent/chocodonut.vim,它仅当您打开一个 chocodonut 类型的文件时执行。您可以将 chocodonut 文件中缩进相关的代码存储在这里。
Vim 有一个颜色runtime路径 (~/.vim/colors/) ,用来存储颜色主题。这个目录中的任何文件都会在命令行命令 :color 中显示出来。
如果您有一个文件 ~/.vim/colors/beautifulprettycolors.vim,当您运行 :color 然后按 Tab,您将会看到 beautifulprettycolors 出现在颜色选项中。 如果您想添加自己的颜色主题,就放在这个地方。
如果您想看其他人做的颜色主题,有一个好地方值得推荐:vimcolors。
Vim有一个语法runtime路径 (~/.vim/syntax/),用来定义语法高亮。
假设您有一个文件 hello.chocodonut,在文件里面有以下内容:
1 | (donut "tasty") |
虽然Vim现在知道了正确的文件类型,但所有的文本都是相同的颜色。让我们添加语法高亮规则,使 “donut” 关键词高亮显示。创建一个新的 chocodonut 语法文件 ~/.vim/syntax/chocodonut.vim,在文件中添加:
1 | syntax keyword donutKeyword donut |
现在重新打开 hello.chocodonut 文件,关键词 donut 已经高亮显示了。
本章不会详细介绍语法高亮。它是一个庞大的主题。如果您感兴趣,可以查阅 :h syntax.txt。
vim-polyglot 插件非常的棒,它提供了很多流行的编程语言的语法高亮。
如果您写了一个插件,您还得创建一个您自己的文档。您可以使用文档runtime路径完成这个。
让我们为 chocodonut 和 plaindonut 关键字创建一个基本文档。创建文件 donut.txt (~/.vim/doc/donut.txt)。在文件中,添加一下内容:
1 | *chocodonut* Delicious chocolate donut |
如果您试着搜索 chocodonut 或 plaindonut (:h chocodonut 或 :h plaindonut),您找不到任何东西。
首先,你需要运行 :helptags来创建新的帮助入口。运行 :helptags ~/.vim/doc/
现在,如果您运行 :h chocodonut 或 :h plaindonut,您将找到上面那些新的帮助入口。注意,现在文件是只读的,而且类型是 “help”。
到现在,本章您学到的所有runtime路径都是自动运行的。如果您想手动加载一个脚本,可使用 autoload runtime路径。
创建一个目录名为 autoload(~/.vim/autoload/)。在目录中,创建一个新文件,起名为 tasty.vim (~/.vim/autoload/tasty.vim)。在文件中:
1 | echo "tasty.vim global" |
注意,函数名是 tasty#donut 而不是 donut()。要想使用autoload功能,井号(#)是必须的。在使用autoload功能时,函数的命名惯例是:
1 | function fileName#functionName() |
在本例中,文件名是 tasty.vim,而函数名是donut。
要调用一个函数,可以使用 call 命令。让我们call这个函数 :call tasty#donut()。
您第一次调用这个函数时,您应当会 同时 看到两条信息 (“tasty.vim global” 和 “tasty#donut”) 。后面再调用 tasty#donut 函数,将只会显示 “testy#donut”。
当您在Vim中打开一个文件,不像前面说的runtime路径,autoload脚本不会被自动加载。仅当您显式地调用 tasty#donut(),Vim才会查找文件tasty.vim,然后加载文件中的内容,包括函数 tasty#donut()。有些函数会占用大量资源,但我们又不常用,这时候 Autoload runtime路径就是最佳的解决方案。
您可以在autoload目录任意添加嵌套的目录。如果您有一个runtime路径 ~/.vim/autoload/one/two/three/tasty.vim,您可以使用:call one#two#three#tasty#donut()来调用函数。
Vim有一个 after runtime路径 (~/.vim/after/) ,它的结构是 ~/.vim/的镜像。在此目录中的任何脚本都会最后执行,所以开发者通常使用这个路径来重载脚本。
比如,如果您想重载 plugin/chocolate.vim 中的脚本,您可以创建~/.vim/after/plugin/chocolate.vim来放置重载脚本。Vim将会先运行 ~/.vim/plugin/chocolate.vim, 然后运行 ~/.vim/after/plugin/chocolate.vim
Vim有一个环境变量 $VIMRUNTIME 用来加载默认脚本和支持文件。您可以运行 :e $VIMRUNTIME查看。
它的结构应该看起来很熟悉。它包含的很多runtime路径都是我们本章前面学过的。
回想第22章,当您打开Vim时,它会在6个不同的位置查找vimrc文件。当时我说最后一个位置就是 $VIMRUNTIME/default.vim,如果Vim在前5个位置查找用户vimrc文件失败,就会使用default.vim 作为vimrc。
不知您是否尝试过,运行Vim是不加载比如vim-polyglot之类的语法插件,但您的文件依然有语法高亮?这是因为当Vim在runtime路径查找语法文件失败时,会从$VIMRUNTIME 的语法目录中查找语法文件。
查阅 :h $VIMRUNTIME了解更多信息。
运行 :set runtimepath?,可以查看您的runtime路径。
如果您使用 Vim-Plug 或其他流行的第三方插件管理器,它应该会显示一个目录列表。比如,我的显示如下:
1 | runtimepath=~/.vim,~/.vim/plugged/vim-signify,~/.vim/plugged/base16-vim,~/.vim/plugged/fzf.vim,~/.vim/plugged/fzf,~/.vim/plugged/vim-gutentags,~/.vim/plugged/tcomment_vim,~/.vim/plugged/emmet-vim,~/.vim/plugged/vim-fugitive,~/.vim/plugged/vim-sensible,~/.vim/plugged/lightline.vim, ... |
插件管理器做了一件事,就是将每个插件添加到runtime路径中。每个runtime路径都有一个类似 ~/.vim/的目录结构。
如果您有一个目录 ~/box/of/donuts/,然后您想将这个目录添加到您的runtime路径中,您可以在vimrc中添加以下内容:
1 | set rtp+=$HOME/box/of/donuts/ |
如果在 ~/box/of/donuts/ 里面,您有一个plugin目录 (~/box/of/donuts/plugin/hello.vim) 以及ftplugin目录 (~/box/of/donuts/ftplugin/chocodonut.vim),当您打开Vim时,Vim将会运行 plugin/hello.vim 中所有脚本。同样,当您打开一个 chocodonut 文件时,Vim 将会运行 ftplugin/chocodonut.vim。
自己试着做一下:创建一个任意目录,然后将它添加到您的 runtimepath中。添加一些我们本章学到的runtime路径。确保它们按预期工作。
花点时间阅读本章,还有认真研究一下这些runtime路径。看一下真实环境下runtime路径是如何使用的。浏览一下您最喜欢的Vim插件仓库,仔细研究一下它的目录结构,您应该能够理解它们中的绝大部分。试着领会重点并跟着做。现在您已经理解了Vim的目录结构,您可以准备学习Vimscript了。
技术上来说,Vim 没有内置的交互式解释器 (REPL),但是有一个 Ex 模式,可以使 vim 像使用 REPL 一样、Ex 模式更现实命令行模式的拓展,即非停止的命令行模式
进入EX模式的方法:
QgQ退出Ex模式
:visual类似于 python 的 print()
echom: 和 echo 相同, 不同支出在于 echom 会将结果保存到 message 历史记录中
查看 message 历史记录
:messages
清空 mwssage 历史记录
:message clear
模式指的是==十进制==数字
1, -1, -10
以 0x 或 oX 开头
以 0b 或 0B 开头
以 0, 0o 或 0O 开头
同 C++ 的数值运算
使用 双引号("") 或 单引号('') 包裹起来
使用 . 连接字符串
1 | :echo "Hello" . "world" |
当使用数字和字符串使用运算符 + - * / 进行算数运算时,vim会将字符串转化为数字
1 | :echo "12 donuts" + 3 |
也可以是字符串与字符串进行数值计算
1 | :echo "12 donuts" * "6 pastries" |
要求:
注意
使用 . 运算符
注意
\n, \"| 程序 | 功能 |
|---|---|
strlen() |
获取字符串长度 |
str2nr() |
将字符串转化为数字 |
str2float() |
将字符串转化为浮点数 |
substitute() |
字符串模式替换 可以结合 getline() 使用 |
| 其他函数 | :h string-functions |
getline(): 获取传入行号的文本
类似于 Python 的列表,类型可以混合在一起
获取单个值
同 Python 的索引,类似于list[n]
切片
list[n:m] 获取 list 列表中的索引号 n 到 m 的元素 (包括m)list[n:]: n到末尾list[:m]: 从头到m注: 若 m > list 的长度,那么就是获取 m 到末尾的元素
同list
使用 +: 连接两个劫镖
| 函数 | 功能 |
|---|---|
len() |
获得列表长度 |
insert() |
将元素传输到列表开始位置 |
remove() |
删除列表元素 |
map()filter() |
用来过滤元素 ==????== |
1 | :let sweeterList = ["glazed", "chocolate", "strawberry"] |
像python
1 | :let favoriteFlavor = ["chocolate", "glazed", "plain"] |
使用 ; 分配剩余的元素
1 | :let favoriteFruits = ["apple", "banana", "lemon", "blueberry", "raspberry"] |
修改一项
1 | :let favoriteFlavor = ["chocolate", "glazed", "plain"] |
修改多项
1 | :let favoriteFlavor = ["chocolate", "glazed", "plain"] |
类似于 python
==区别==在于 vim 只使用字符串为“键”,即使使用数字,也会将数字转化为字符串
使用 #{} 来表示
1 | :let mealPlans = #{breakfast: "waffles", lunch: "pancakes", dinner: "donuts"} |
要求
“键”的字符必须满足一下条件:
_).-).[]
1 | meal['breakfast'] |
.
1 | meal.lunch |
[]`
1 | meal['breakfast'] = "..." |
.
1 | meal.lunch = "..." |
| 函数 | 功能 |
|---|---|
len() |
获取字典长度 |
has_key() |
获取字典的键 |
empty() |
字典是否有键值对 |
remove() |
移除键值对 |
items() |
将键值对转化为列表:每个键值对为一个列表 |
filter() and map() |
过滤作用 |
v:falsev:truev:nonev:null注:
v:是 vim 内置的变量- 这些不常用
- 常用如下
- 布尔值:0(假);non-0 (真)
- 空字符串:
""
1 | :echo json_encode({"test": v:true}) |
1 | :echo json_encode({"test": v:false}) |
1 | :echo json_encode({"test": v:none}) |
被解释成 null
1 | :echo json_encode({"test": v:null}) |
类似于 v:none
==!=>>=<<=注意:这里的字符串仍被转换为数字
用关系运算符来比较字符串
=~:对字符串进行正则表达式匹配:可以模式匹配上!~: 对字符串进行正则表达式匹配:不可以匹配上==: 完全匹配:相等!=:完全匹配:不相等注意:如果在设置了
set ignorecase,那么比较时也会忽略大小写
设置总是忽略大小写
在末尾加一个 #,如:=~# (推荐:安全)
1 | echo str =~# "hearty" |
在末尾加一个 ?,如:=~?
1 | echo str =~? "hearty" |
1 | if {clasuse} |
1 | if {predicate1} |
1 | {predicate} ? expressiontrue : expressionfalse |
补充
background: 背景strftime(): 目前时间||
注意:短路运算
&&
注意:短路运算
1 | let breakfasts = ["pancakes", "waffles", "eggs"] |
1 | let meals = [["breakfast", "pancakes"], ["lunch", "fish"], ["dinner", "pasta"]] |
1 | let beverages = #{breakfast: "milk", lunch: "orange juice", dinner: "water"} |
1 | let counter = 1 |
1 | let current_line = line(".") |
和 python 一样:中断循环
和 python 一样:跳过当前轮
和 python 一样
注
throw:抛出一个错误
catch 捕捉的错误
:h :catch
1 | catch /^Vim:Interrupt$/. " catch interrupts (CTRL-C) |
定义变量
1 | let val = "value" |
let 定义的是==变量==
修改变量
当需要改变变量的值时,需要使用 let
1 | let val = "value" |
使用变量
1 | echo val |
定义
使用 const
1 | const val = "value" |
vim 可以访问==终端的环境变量==
语法
1 | ${Enviroment_variable} |
示例
1 | echo $SHELL |
注意:不要使用
{},错误的用法:echo ${SHELL}
这些变量也就是 .vimrc 中使用 set 设置的那些变量
访问
方法1: 使用 &`
1 | echo &background |
方法2: set background?
访问
使用 @
更新寄存器的值
使用 let
示例
访问寄存器 a
1 | echo @a |
更新寄存器 a 的值
1 | let @a .= " donut" |
| 变量范围 | 说明 |
|---|---|
g: |
Global variable |
{nothing} |
Global variable |
b: |
Buffer-local variable |
w: |
Window-local variable |
t: |
Tab-local variable |
s: |
Sourced Vimscript variable |
l: |
Function local variable |
a: |
Function formal parameter variable |
v: |
Built-in Vim variable |
范围
可以在任何地方使用
定义
let val = "value"let g:val = "value"使用
echo valecho g:val定义
添加前缀 b:
1 | const b:donut = "chocoldate donut" |
说明
定义的每个缓冲区变量仅在定义时所在的缓冲区有效,也就是说缓冲区变量的作用域是定义所在缓冲区
特殊缓冲区值
b:changedtick: 跟踪了在当前缓冲区所有的改变
定义
添加前缀 w:
1 | let w:donut = "donut" |
说明
类似于缓冲区变量,变量的作用域为定义的窗口
标签变量
定义
添加前缀 t:
1 | let t:done |
说明
类似于窗口变量和缓冲区变量
定义
使用前缀 s:
说明
仅在定义变量的脚本内部才可以访问到
定义
l:a:说明
前缀为 v:
注意
无法定义这些变量
常见
| 变量 | 说明 |
|---|---|
v:version |
vim 版本 |
v:key |
contains the current item value when iterating through a dictionary. |
v:val |
contains the current item value when running a map() or filter() operation. |
v:true, v:false, v:null, and v:none |
special data types. |
注意:查看其他的内置变量
:h vim-variableor:h v:.
1 | function {FunctionNmae}() |
注意:
- ==函数名必须以大写字母开头==
- 函数名不能以数字开头
使用脚本变量前缀 s:,如:function s:tasty()
不允许有重名函数
覆写函数
在 function 后面使用 !
1 | function! Tasty() |
覆写之后上面的函数就会没有。==覆写不是重载==哦
使用 :function
:function Tasty
:function /pattern
使用 :verbose 和 :function 命令
某一函数
1 | :verbose function <FunctionName> |
模式匹配函数
1 | :verbose function /<pattern> |
删除一个已有函数: :delfunction {FunctionName}
例如: :delfunction Tasty
return: 等价于返回0用法
使用 a:
示例
如函数 Tasty() 使用参数 food
1 | function! Tasty(food) |
使用 l:
问题 1
若在函数内部定义一个变量,那么这个变量默认为局部变量
1 | function! Tasty() |
与下面代码等价,但是==没有下面的代码好==
1 | function! Tasty() |
问题 2
1 | function! Calories() |
变量会与特殊变量同名,导致错误
错误根源:let count = "count"
纯粹调用函数
使用 call 调用函数
命令行
1 | :call Tasty() |
脚本
1 | call Tasty() |
You cannot call a command-line command with another command-line command.
你不能使用命令行命令调用命令行命令
使用函数返回值
使用 call("<FunctionName>", [<参数列表>])
[] 不可以省略
:call 是命令行命令call(): 函数使用 =
1 | function! Breakfast(meal, beverage = "Milk") |
使用 ...
1 | function! Buffet(...) |
a:0: 共有多少个参数a:1 是第一个参数a:2 是第二个参数a:20 是第20个参数a:000 是所有参数值组成的列表a:0 示例
1 | function! Buffet(...) |
a:000 + for
1 | function! Buffet(...) |
在定义函数的第一行最后加入 range
会给该函数添加两个特定变量:
a:firstline: 所选范围的第一行的行号b:lastline: 所选范围的最后一行的行号line("."): 会在所选范围的所有行上,每行会执行一次函数range: 只会在第一行执行一次1 | function! Breakfast() range |
1 | :11,20 call Breakfast |
没什么用处
也没怎么看懂
类似于函数指针,将变量指向一个函数
使用 function()
1 | function! Breakfast(item) |
未命名函数
1 | let Plus = {x,y -> x + y} |
You can call a function from insisde a lambda expression(没看懂)
使用 ->
->跟在函数名后面,且中间不能添加空格
1 | Source->Method1()->Method2()->...->MethodN() |
像 shell 的 ==管道==
1 | function! Lunch() |
appetizer在Lunch函数中定义,该函数返回SecondLunch函数。注意,SecondLunch使用了appetizer,但在Vimscript中,它没有访问该变量的权限。如果你尝试运行echo Lunch()(), Vim将抛出一个未定义变量错误。
解决
1 | function! Lunch() |
略