使用 Shell 命令来对 Unix 时间戳和日期进行转换 date 命令

在程序中经常要使用到 Unix timestamp 和日期的转换,通常情况下都是 Google 一个时间戳转换的网页在进行转换,其实 Linux 命令中就有能够快速实现转换的命令。主要都是集中在 date 这个命令。date 命令主要用于显示或设定系统时间和日期。

修改系统的时区

Linux 用来修正系统的时区

sudo dpkg-reconfigure tzdata

选择 Asia > Shanghai

date 常用命令

获取当前的 Unix timestamp

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

将 string 日期转成日期

使用 -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

reference


2017-09-04 shell , linux

Mastering the Vim

我已经用了很长一段时间 Vim 了,但是 Vim 最可贵之处便在于你永远达不到 Vim 的天花板,在使用的过程中我永远会发现操作 Vim 的其他便捷方法。最近看了一个关于 Vim 的讲座 ,革新我对 Vim 命令的认识。可以说掌握这样的一个世界观可以对 Vim 的操作上到另外一个层次。下面就总结一下这个视频中的精髓内容。

Text Objects and motions

@ChrisToomey 定义了一种 Vim Language,Vim 的语法由数词 + 动词 + 名词 组成,比如:

d 删除
w 单词
将两个字母组合起来就是 删除单词

这个经常使用的命令非常容易记住。如果想要删除三个单词,则是 3dw,所以可以总结出

{number}{command}{text-object}

这样的形式

  • 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

DOT command

Vim 中的 “.” 命令,以命令为单位,重复上一个命令。

Sublime , IntelliJ IDEA 中经常被人提及的 multiple cursor 的功能,能够在编辑器中提供多个光标一起编辑,其实 Vim 不需要多光标就能够做到,结合强大的 .n . 可以快速的编辑大量重复的内容。

比如在 Vim 中的 workflow 就是

  • 使用 /pattern 来所有需要编辑的内容
  • 使用可编辑的 edit,比如 cw 当前单词 ESC 退出,一个完成的动作
  • 使用 n 来找到下一个匹配的地点
  • 使用 . 来重复编辑操作,可以直接将单词替换为上一个动作编辑
  • 然后重复上面两个步骤,整个文件即可替换完成

macro

Vim 允许记录一个宏来完成一组命令

qa                  # 将命令记录到寄存器 a 中
q                   # 再次 q 结束记录
@a                  # 使用寄存器
@@                  # 使用上一次寄存器

refernence


2017-09-03 vim , linux , editor

Raspberry pi 自动挂载 NTFS USB 设备

一些相关的命令

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

2017-09-02 linux , raspberrypi , mount , ntfs , usb

Spring MVC 应用处理 CORS

什么是跨域或者说什么是CORS(Cross-origin resource sharing),中文叫”跨域资源共享”。在了解 CORS 之前首先要知道“同源策略”,出于安全考虑,浏览器会限制Ajax中发起的跨站请求。比如,使用 XMLHttpRequest 对象发起 HTTP 请求就必须遵守同源策略(same-origin policy),”同源策略“是浏览器安全的基石。具体而言,Web 应用程序能且只能使用 XMLHttpRequest 对象向其加载的源域名发起 HTTP 请求,而不能向任何其它域名发起请求。阮一峰写的一篇关于 CORS 的文章 介绍得非常详细,这里主要记录一下重点以及 Spring MVC 中如何处理 CORS。

CORS 做到了不破坏即有规则,只要服务端实现了 CORS 接口,就可以跨源通信。

简单请求 VS 非简单请求处理

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

需要同时满足以下两大条件,才属于简单请求。

  • 请求方法仅仅为以下三种方法之一:

    HEAD、GET、POST

  • HTTP的头信息不超出以下几种字段:

    Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

简单请求处理

Response Header 选项
Access-Control-Allow-Origin 必须,值要么是请求的 Origin,要么是 * ,表示接受所有域名请求
Access-Control-Allow-Credentials 该字段可选。它的值是一个布尔值,表示是否允许客户端发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
Access-Control-Expose-Headers 该字段可选。扩展客户端能够访问的字段。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以返回FooBar字段的值。

简单请求的处理过程可以参考下图:

简单请求流程

对于简单请求,CORS 的策略是请求时,在头信息中添加一个 Origin 字段,服务器收到请求后,根据该字段判断是否允许该请求。

  • 如果允许,则在 HTTP 头信息中添加 Access-Control-Allow-Origin 字段,并返回正确的结果
  • 如果不允许,则不在头信息中添加 Access-Control-Allow-Origin 字段。

浏览器先于用户得到返回结果,根据有无 Access-Control-Allow-Origin 字段来决定是否拦截该返回结果。

script 或者 image 标签触发的 GET 请求不包含 Origin 头,所以不受到 CORS 的限制,依旧可用。如果是 Ajax 请求,HTTP 头信息中会包含 Origin 字段,由于服务器没有做任何配置,所以返回结果不会包含 Access-Control-Allow-Origin,因此返回结果会被浏览器拦截,接口依旧不可以被 Ajax 跨源访问。

非简单请求

而对于真正实现中的请求,可能会使用 Content-Type:application/json,也有可能有自定义 Header,所以了解非简单请求的处理也非常必要。

对于 Content-Typeapplication/json 的特殊请求,需要服务端特殊对待的请求,在正式通信前会增加一次“预检”请求(preflight)。浏览器会先询问服务器,当前网页所在的域名是否在服务器的许可名单,以及可以使用哪些HTTP动词和头信息,得到服务端回复才会发出正式的请求,否则报错。

CORS 请求相关 Header

Request Header value
Access-Control-Request-Method 真实请求使用的 HTTP 方法
Access-Control-Request-Headers 真实请求包含的自定义 Header

在服务端收到客户端发出的预检请求后,校验 OriginAccess-Control-Request-MethodAccess-Control-Request-Headers,通过校验后在返回中加入如下的header:

Response Header value
Access-Control-Allow-Methods 必须,值为逗号分割的字符串,表明服务器支持的所有跨域请求方法,返回的是所有支持的方法,为了避免多次“预检”请求。
Access-Control-Allow-Headers 如果浏览器请求包括 Access-Control-Request-Headers 字段,则 Access-Control-Allow-Headers 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。
Access-Control-Allow-Credentials 与简单请求含义相同
Access-Control-Max-Age 该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

非简单请求流程

Spring 处理跨域

这里主要针对 Spring 3.x 来处理, 在 Spring 4.2 之后官方引入了 @CrossOrigin 注解,处理 CORS 变的非常方便。所以接下来就记录下 3.x 中的处理方法。

更新 web.xml

更新 web.xml 让 Spring 开启 OPTIONS 处理.

<servlet>    
   <servlet-name>application</servlet-name>    
   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
   <init-param>  
		<param-name>dispatchOptionsRequest</param-name>  
		<param-value>true</param-value>  
   </init-param>    
   <load-on-startup>1</load-on-startup>    
</servlet>    

添加Header

使用 Interceptor

public class CorsInterceptor extends HandlerInterceptorAdapter {

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		addOriginHeader(request, response);
		if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
			response.setStatus(200);
			return false;
		}	
		return true;
	}

	private void addOriginHeader(HttpServletRequest request, HttpServletResponse response) {
		String origin = request.getHeader("Origin");
		response.addHeader("Access-Control-Allow-Origin", origin);
		response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type");
		response.addHeader("Access-Control-Allow-Credentials", "true");         // 可选,是否允许Cookie
		response.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
		response.addHeader("Access-Control-Max-Age", "1728000");
	}
}

在 XML 中配置 Interceptor

然后在 Controller 中

@RequestMapping(value = "/test/hello", method = {RequestMethod.GET, RequestMethod.OPTIONS})

然后OK


2017-09-01 Spring , CORS , JS , Web , HTTP , 跨域,

爬虫相关材料整理

这篇文章用来收集整理爬虫相关的资料。

相关技术

如果只想单纯的自己开发,可以使用 Python + Celery + Redis/MySQL 基本能满足 80% 的需求。

如果想要选用框架 Scrapy,pyspider,等等都是非常不错的选择,我甚至在 GitHub 上看到过 Java 的分布式爬虫。

书籍

Python 3 网络爬虫开发实战

这本书在网上有部分 gitbook,链接在这里

网上公开的部分都是无关痛痒的部分,不过提及的工具倒是可以参考一下。大部分我之前的文章也都有提及


2017-08-29 collection , spider , crawler , python , redis , mysql

树莓派系统安装及设置

树莓派官网有很多系统可以选择,我选了官方维护的 Raspbian 基于 Debian 的衍生版,主要是熟悉他的 APT 包管理,看评价三方维护的 Snappy Ubuntu Core 换用了其他的 snap 的管理,不是很了解,所以还是选择了 Raspbian。

系统安装

官网提供的教程非常方便, 采用开源的镜像烧录工具 Etcher 非常方便的就可以在三大平台上完成镜像到 SD 的烧录。当然如果熟悉各个平台的工具也可以自己手动完成烧制。

启动系统

在将系统写入 microSD 卡之后,将卡插入树莓派板子,启动树莓派,开机即可,可以用 HDMI 接口连接显示器,用一个外接键盘来输入。树莓派的默认用户名是: pi ,默认密码为: raspberry

root 账户

使用如下命令给 root 账户设置密码并允许登录

sudo passwd root
# 然后输入密码
# 用同样的方式修改默认账户 pi 的密码
sudo passwd pi

启用 SSH

raspbian 自带 SSH ,启动

sudo service ssh start

其他配置

raspi-config

运行该命令可以看到一系列的选项,比如修改 Hostname ,修改密码等等选项,可以配置一下。

更换 sources.list

换用清华的源 : https://mirror.tuna.tsinghua.edu.cn/help/raspbian/

必备应用

apt install vim
apt install samba samba-common-bin
apt install zsh
apt-get install nginx
sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
chsh -s /bin/zsh
apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \
libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev
curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash
apt install mysql-server
apt-get install libmysqlclient-dev

2017-08-26 raspberrypi , linux

Redis 安全性检查

Redis 在设计上,是用来被可信客户端访问的,也就意味着不适合暴露给外部环境非可信客户端访问。

最佳的实践方法是在 Redis 前增加一个访问控制层,用于校验用户请求。

基本配置

Redis 本身提供了一些简单的配置以满足基本的安全控制。

  • IP 绑定。如果不需要直接对外提供服务,bind 127.0.0.1 就行了,切忌 bind 0.0.0.0
  • 端口设置。修改默认的 6379,一定程度上避免被扫描。
  • 设置密码。Redis 的密码是通过 requirepass 以明文的形式配置在 conf 文件里的,所以要尽可能得长和复杂,降低被破解的风险。因为 redis 非常快,外部环境可以在一秒内 150k 次暴力破解,所以配置密码一定要复杂。
  • 重命名或禁用某些高危操作命令。向 config、flushall、flushdb 这些操作都是很关键的,不小心就会导致数据库不可用。可以在配置文件中通过 rename-command 重命名或禁用这些命令。

网络配置

对于直接暴露在互联网的 Redis,应该使用防火墙阻止外部访问 Redis 端口。客户端应该只通过回环接口访问 Redis。

在 redis.conf 文件添加

bind 127.0.0.1

由于 Redis 设计的初衷,如果不能成功阻止外部访问 Redis 端口,会有很大的安全影响。外部攻击者使用一个 FLUSHALL 命令就可以删除整个数据集。

使用密码

虽然 Redis 没有实现访问控制,但是提供了一个简单的身份验证功能。

在配置文件中修改:

requirepass mypassword

重启 redis

sudo service redis-server restart

登录验证

./redis-cli -h 127.0.0.1 -p 6379 -a mypassword
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "mypassword"

如上输出,配置正确。也可以在连接之后使用 auth 验证

./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> auth mypassword
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "mypassword"

通过修改 server 的 conf 文件方式需要重启 server, 也可以通过客户端来修改密码

127.0.0.1:6379> config set requirepass mypassword
OK

使用客户端设置的密码,Redis 重启之后还会使用 redis.conf 配置文件中的密码。

设置密码之后 Redis 集群中 slave 中也需要配置 和 master 一样的密码

masterauth master-password

身份验证是一个可选的冗余层,如果防火墙或者其他保护 Redis 安全的系统被攻破,对于不知道授权密码的情况,攻击者依然不能访问 Redis。

auth 命令是明文传输的,所以依然不能阻止那些获得网络访问权限的攻击者嗅探。

reference


2017-08-25 redis , database , nosql , security , key-value , db

Linux 主机在线监控: nodequery

很久没有更新这个分类下的文章了,其实一直在体验不同的产品,只是真的很少有能拿出来讲一下的东西。不管是硬件还是软件,最近几年使用的东西越来越狭窄,越来越收缩,当然对于某一个特定的需求,总有一个产品能够占领绝大多数市场,而也有部分产品能够瓜分小众市场。这里要介绍的这个 NodeQuery 就不是一个大而全的产品,而是一个很精细的小众产品。我用它也一年多了,我的需求很简单,能够实时监控我的 VPS,能够在宕机或者高负载时报警。NodeQuery 完全能够满足我的需求。

用 NodeQuery 自己的话描述自己就是:”一个轻量、易用的 Linux 服务器监控服务”.

NodeQuery provides a lightweight and easy to use Linux server monitoring service.

NodeQuery 免费账户可以提供 10 台机器的监控,

官网地址: https://nodequery.com/

界面展示

index

details

使用

同样使用也非常方便,新建 Server,然后会给出一个一键脚本,在自己的 VPS 上一键安装就行,脚本同样是开源的托管在 GitHub 上。人人都可以审查。

移除 nodequery

2021 年 10月更新

很多年没有上 NodeQuery 查看,发现 NodeQuery 已经不更新了,这里记录一下移除 NodeQuery 的命令:

rm -R /etc/nodequery && (crontab -u nodequery -l | grep -v "/etc/nodequery/nq-agent.sh") | crontab -u nodequery - && userdel nodequery

API

这个网站也能够提供 API 支持,能够读取历史情况下 Server 的状态,目前写功能暂时还无法使用。

reminder

不过需要提醒的是,这个网站自从 2014 年起就再没有更新,不清楚背后发生了什么事情,但是也是感到非常的可惜。


2017-08-23 linux , vps , monitor

Python 笔记之内存模型 Variables Objects and References 区别

许多使用静态语言比如 C、 C++ 或者 Java 的人,在转到 Python 的时候可能第一个会疑惑的就是 Python 不需要显示的指定类型,那么 Python 是怎么知道变量的类型呢?

变量创建流程

在 Python 中,变量的创建遵循着一个非常合理的方式,以 a=3 来举例子:

  • 变量创建

    一个变量(名字)比如 a ,当第一次被赋值时被创建。

  • 变量类型 Variable Types

    一个变量永远不会有任何类型信息或者约束,类型的概念和 Object 关联,而不是变量名字。变量都是通用的(泛型),变量总是在特定时间指向一个特定的 Object 。

  • 变量使用 Variable use

    当变量出现在表达式中,他会立即使用当前指向的 Object 替换。因此,所有的变量在使用之前都必须显式的被赋值,当使用未被赋值的变量时会产生错误。

总结来说,变量会在赋值时被创建,并且能够指向任何类型的对象,在引用前必须已经被赋值。这就是动态类型模型和传统静态语言最显著的差别。

所以对于 a=3 Python 会执行三个完全不同的步骤来完成,下面的步骤显示了 Python 语言中所有赋值会执行的步骤:

  1. 创建一个对象来表示 3
  2. 如果变量不存在,则创建一个变量 a
  3. 将变量 a 连接到对象 3

变量和对象会分别保存到不同的内存中。变量永远会指向 Objects,永远不会指向其他变量,但是大型的 Objects 可能会有指向其他 Objects 的链接(比如 list 对象就可能会有很多指向其他对象的链接)。

在 Python 中,这些从变量指向对象的链接被称为引用 references ,也就是说引用是一种关联,在内存中表现为指针。每当一个变量被使用, Python 会自动跟随着 variable to object 链接。

所以这些术语理解起来要简单得多,具体来讲:

  • Variables 变量是 system table 的记录,使用空间来记录指向 Objects 的链接
  • Objects 对象是真正分配内存的,使用足够的空间来表示他们真的值
  • References 引用 会自动跟随着指针从变量到对象

概念上讲,每一次创建新的值, Python 都会创建新的对象(开辟内存空间)来表示值。内部来说,作为优化,Python 会使用缓存重用一些特定的不会改变的对象,比如比较小的 integers,strings,比如 0 并不会单独每一次都开辟内存空间,而是使用缓存。但是从逻辑上,每一个表达式的结果都会为不同的对象,每一个对象都有自己的内存空间。

每一个对象都有两个标准的 header fields:type designator 用来表明对象的类型,reference counter 来记录引用次数,何时对象应该被回收。

Types live with Objects, Not Variables

对象回收

当对同一个变量重复赋值,那么变量之前指向的 Object 会发生什么?

a = 3
a = 'Spam'

当 a 被重新赋值为 Spam 时,对象 (3) 会发生什么。在 Python 中,当一个变量名被赋值到新对象时,之前对象的空间会被回收(也就是说当当一个对象不再被变量或者其他对象引用时会被回收)。这种自动回收对象空间的机制被称为 垃圾回收 (garbage collection)。

内部实现来说,Python 通过在每一个 Object 中存储一个 counter,来追踪当前对象被引用的次数来实现垃圾回收。一旦引用计数变为 0,对象的内存空间就会被自动回收。

Shared References

上面的内容都是单一变量,如果出现变量赋值比如

a = 3
b = a

这个时候 Python 会怎么处理呢? 当变量 b 被创建时,会创建一个从 b 指向对象 3 的引用。这个有多个变量名字指向相同的对象的场景,被称为 shared reference

在上面的基础上,如果

a = 'spam'

a 被重新赋值 spam 这个时候不会影响 b 指向的对象 3.

如果

a = a + 2

同样不会影响 b 的值,对象 integer 5 会作为加号的结果放到新的内存空间,是一个新的对象,所以也不会改变 b 的值。事实上,也没有任何方法可以改变对象 3 的值,就像之前关于 Python 类型一文 中说的那样,integer 是不可变类型,因此不能原地修改其内容。

共享引用和原地修改

当使用可变对象,比如 list 时,如果存在共享引用,要特别注意,当修改其中一个引用的对象的值时,会影响其他指向这个对象的引用。

L1 = [2,3,4]
L2 = L1
L1[0] = 'spam'

这个时候 L1,L2 变量指向的对象值都被改变了。

如果需要深度拷贝 list 时,就需要特别注意,不要使用引用。

L1 = [2,3,4]
L2 = L1[:]             # 创建了一个 L1 的拷贝
L1[0] = 'spam'          # 此时再修改 L1 则不会对 L2 造成影响

对 L1 的修改不会影响 L2 ,因为 L2 指向和 L1 完全不同的内存区域。

import copy
X = copy.copy(Y)            # make top-level "shadow" copy of any object Y
X = copy.deepcopy(Y)        # make deep copy of any object Y: copy all nested parts

判别相等

因为 Python 的引用模型,所有有两种完全不同的判断相等的方法

L = [1,2,3]
M = L
L == M                      # Same 这时候判断的是引用是否相同,判定值是否相同
L is M                      # Same is 操作符判断是指向的对象是否相同,更强的相等判断

更比如说

L = [1,2,3]
M = [1,2,3]
L == M                      # Same 相同的内容
L is M                      # False 不同的对象

再比如

X = 42
Y = 42                      # 应该是两个不同的对象
X == Y                      # True
X is Y                      # 相同的对象,caching 在起作用

之前说过 Python 垃圾回收时,优化机制,导致 small integer 和 strings 会被缓存和重新使用,is 操作符告诉我们他们引用得是相同的对象。

事实上,如果想要知道对象的引用次数,可以使用 getrefcount 方法,在 sys 模块中。

import sys
sys.getrefcount(1)

检查对象 1 被引用的次数,常常会发现超过期待。因为 Integer 是不可变的,所以也就无所谓多少引用指向相同的对象了。

reference

  • Learning Python 4th Edition Chapter 6

2017-08-22 python , variable , object

Python modules and package

Python 很重要的一个概念 module,用来组织代码结构。

import 导入搜索的路径

  • 代码的 home 路径
  • PYTHONPATH 目录(如果存在的话)
  • 标准库路径
  • .pth 文件中配置的路径(如果存在的话)

最终,这些路径都会存在 sys.path 中,是一个保存着一系列搜索路径的 list。

>>> import sys
>>> sys.path

导入工作流程

  • 在路径中找到导入的模块
  • 编译
  • 运行

Package

一个目录的 Python code 被称为 package,这样的导入被成为 package import。

import dir1.dir2.mod
from dir1.dir2.mod import x

文件目录结构可能是

dir0\dir1\dir2\mod.py

每一个 package 被定义时都会产生一个 __init__.py 的文件,该文件可以像普通文件一样包含 Python 代码,也可以为空。

Package 初始化

当 Python 导入 Package 时,会自动跑 `__init__.py` 下的内容,所以 `__init__.py` 文件下是天然的存放初始化内容的地方,比如初始化数据库连接等等。

如果定义了 __all__ ,在 from * 时导入,就会选择性导入指定内容

__all__ = ["Error", "encode", "decode"]

2017-08-20 python , modules , import , pythonpath , library

电子书

本站提供服务

最近文章

  • Dinox 又一款 AI 语音实时转录工具 前两天介绍过 [[Voicenotes]],也是一款 AI 转录文字的笔记软件,之前在调查 Voicenotes 的时候就留意到了 Dinox,因为是在小红书留意到的,所以猜测应该是国内的某位独立开发者的作品,整个应用使用起来也比较舒服,但相较于 Voicenotes,Dinox 更偏向于一个手机端的笔记软件,因为他整体的设计中没有将语音作为首选,用户也可以添加文字的笔记,反而在 Voicenotes 中,语音作为了所有笔记的首选,当然 Voicenotes 也可以自己编辑笔记,但是语音是它的核心。
  • 音流:一款支持 Navidrom 兼容 Subsonic 的跨平台音乐播放器 之前一篇文章介绍了Navidrome,搭建了一个自己在线音乐流媒体库,把我本地通过 [[Syncthing]] 同步的 80 G 音乐导入了。自己也尝试了 Navidrome 官网列出的 Subsonic 兼容客户端 [[substreamer]],以及 macOS 上面的 [[Sonixd]],体验都还不错。但是在了解的过程中又发现了一款中文名叫做「音流」(英文 Stream Music)的应用,初步体验了一下感觉还不错,所以分享出来。
  • 泰国 DTV 数字游民签证 泰国一直是 [[Digital Nomad]] 数字游民青睐的选择地,尤其是清迈以其优美的自然环境、低廉的生活成本和友好的社区氛围而闻名。许多数字游民选择在泰国清迈定居,可以在清迈租用廉价的公寓或民宿,享受美食和文化,并与其他数字游民分享经验和资源。
  • VoceChat 一款可以自托管的在线聊天室 VoceChat 是一款使用 Rust(后端),React(前端),Flutter(移动端)开发的,开源,支持独立部署的在线聊天服务。VoceChat 非常轻量,后端服务只有 15MB 的大小,打包的 Docker 镜像文件也只有 61 MB,VoceChat 可部署在任何的服务器上。
  • 结合了 Google 和 AI 的对话搜索引擎:Perplexity AI 在日本,因为 SoftBank 和 Perplexity AI 开展了合作 ,所以最近大量的使用 Perplexity ,这一篇文章就总结一下 Perplexity 的优势和使用技巧。