| Yang |


  • Home

  • Tags

  • Archives

Git实战之高级篇

Posted on 2019-03-06

分离HEAD

HEAD是一个对当前检出记录的符号引用,也就是指向你正在其基础上进行工作的提交记录。

HEAD总是指向当前分支上最近一次提交记录。

分离的HEAD就是让其指向了某个具体的提交记录而不是分支名,使用git checkout x实现。

相对引用

相对引用让我们可以避免直接使用哈希值去指定提交记录,常用的有:

  • 使用^向上移动1个提交记录
  • 使用~<num>向上移动多个提交记录

撤销变更

Git Reset

git reset通过把分支记录回退几个提交记录来实现撤销改动。你可以将这想象成“改写历史”。git reset向上移动分支,原来指向的提交记录就跟从来没有提交过一样。

只对本地分支有效。

Git Revert

生成一个和当前提交完全相反的提交,来达到撤销的目的,适用于远程。

自由修改提交树

Git Cherry-pick

git cherry-pick <提交号>...实在是很简单。。。

Git Rebase

当你知道你所需要的提交记录(并且还知道这些提交记录的哈希值)时, 用cherry-pick再好不过了。但是很乱的时候,就可以用交互式的git rebase来解决,帮你从一系列的提交中找到你想要的。

交互式rebase指的是使用带参数--interactive的rebase命令, 简写为-i

参数详情待添加,平时git rebase -i之后-f参数用得最多。

提交的技巧

Git Rebase

假设我们在newImage分支上进行一次提交,然后又基于它创建了cpation分支,然后又提交了一次,这个时候我们想对之前的提交记录有一些小小的调整,emmm还是很常见的,我们可以这么来

1
2
3
4
$ git rebase -i HEAD~2
$ git commit --amend
$ git rebase -i HEAD~2
$ git rebase caption maste

效果图

Git Cherry-pick

当然我们也可以直接使用git cherry-pick xxx达到我们的目的。

Git Tags

分支很容易被人为移动,且当有新的提交时,它也会移动。Tags便是永远指向某个提交的标识。

1
2
3
4
$ git checkout c1
$ git tag v0
$ git checkout c2
$ git tag v1

Tags

Git Describe

由于标签在代码库中有“锚点”的作用,Git设计了git describe的命令来描述离标签最近的点。

pass

Git实战之基础篇

Posted on 2019-03-06

写在最前

之前有写过对Git的一点点使用,但是不会的还是太多了。

趁着鹏飞讲了Git,自己也跟着走一走。

Git Commit

Git仓库中的提交记录保存的是你目录下所有文件的快照,同时Git希望提交记录尽量轻量,因此在你每次进行提交时,它并不会盲目地复制整个目录。条件允许的情况下,它会将当前版本与仓库中的上一个版本进行对比,并把所有的差异打包到一起作为一个提交记录。当然Git也保存了提交的历史记录。

1
2
3
$git commit 
# 不过公司提交一般要求加上备注
$git commit -m 'your comment'

Git Branch

Git 的分支也非常轻量。它们只是简单地指向某个提交纪录 —— 仅此而已。网上大佬们都说:
早建分支!多用分支!

因为创建再多的分支也不会造成存储或者内存上的开销,并且按照逻辑分解到不同的分支比维护臃肿的分支简单很多。

使用分支可以理解为“我想基于这个提交以及他的所有父提交进行新的工作”

1
2
3
4
# 创建新分支
$git branch bugFix
# 切换到新分支
$git checkout bugFix

Git Merge

在我们新建好分支且完成新功能的开发之后,这个时候我们需要将其合并回主线。我们先来看看第一种方法git merge。

1
2
3
4
5
6
$ git branch bugFix
$ git checkout bugFix
$ git commit
$ git checkout master
$ git commit
$ git merge bugFix

Merge

Git Rebase

git rebase同样是合并分支的方法,其实质是取出一系列的提交记录,“复制”他们,然后再另一个地方逐个的放下去。

Rebase的优势是可以创造更线性的提交历史,因为如果只使用Rebase的话,代码库的提交历史将会变得异常清晰。

1
2
3
4
5
6
7
$ git branch bugFix
$ git checkout bugFix
$ git commit
$ git checkout master
$ git commit
$ git checkout bugFix
$ git rebase master

Rebase

Spark学习笔记之SparkSQL

Posted on 2019-03-05

SparkSQL简介

基本特点

SparkSQL是Spark用来操作结构化和半结构化数据的接口,其提供了三大功能:

  • SparkSQL可以从各种数据源中读取数据(JSON,Hive…)
  • SparkSQL不仅支持Spark程序内部使用SQL查询,也支持外部工具(Tableau等)使用JDBC等进行连接
  • SparkSQL支持SQL与常见的Python/Java/Scala等代码高度整合,包括连接RDD和SQL表,公开的自定义SQL函数接口等。

其运行原理也十分简单:把Spark SQL转化为RDD,然后提交到集群运行。

SparkSession

SparkSession是Spark2.0引入的新概念。为用户提供了一个统一的切入点,让用户来使用Spark的各项功能。

在spark的早期版本中,SparkContext是spark的主要切入点,由于RDD是主要的API,我们通过sparkcontext来创建和操作RDD。对于每个其他的API,我们需要使用不同的context。例如,对于Streming,我们需要使用StreamingContext;对于sql,使用sqlContext;对于Hive,使用hiveContext。但是随着DataSet和DataFrame的API逐渐成为标准的API,就需要为他们建立接入点。所以在spark2.0中,引入SparkSession作为DataSet和DataFrame API的切入点,SparkSession封装了SparkConf、SparkContext和SQLContext。为了向后兼容,SQLContext和HiveContext也被保存下来。

SparkSession是SQLContext和HiveContext的组合,所以两者的API在SparkSession上都是可用的。SparkSession内部封装了SparkContext,其计算是由SparkContext完成的。

与Spark交互的时候不需要显示地创建SparkConf,SparkContext和SQLcontext,这些对象已经封闭在了SparkSession中。

使用SparkSQL

DataFrame

在Spark中DataFrame是一种以RDD为基础的分布式数据集,类似于我们传统数据库中的表。其与RDD的区别是DataFrame带有Schema元信息,即DataFrame所表示的二维数据集的每一列都带有名称和类型。SparkSQL依据此进行了优化,以大幅提升运行时的效率。

RDD与DataFrame

读取和存储数据

SparkSQL支持很多结构化的数据源,包括Hive表、JSON和Parquet文件等等,也指定结构信息可以把RDD转化为DataFrame。

Hive

SparkSQL对Hive的支持十分友好。支持Hive的支持的任何存储格式(Serde)。

我们提供一份Hive配置即可把SparkSQL连接到已经配置好的Hive上,具体操作Goole一下就好。

示例

1
2
3
4
5
from pyspark.sql import HiveContext

hiveCtx = HiveContext(sc)
rows = hiveCtx.sql("select key,value from my_table")
key = rows.map(lambda row:row[0])

Parquest

Parquest是一种流行的列式存储格式,可以高效地存储具有嵌套字段的记录。

首先我们可以通过HiveContext.parquestFile或者SQLContext.parquestFile来读取数据

1
2
3

rows = hiveCtx.parquestFile(parquestfile)
names = rows.map(lambda row:row.name)

其次我们也可以把Parquest文件注册为SparkSQL的临时表,然后再这张表上进行查询。

JSON

我们只需要调用hiveCtx中的jsonFile()方法即可读取JSON数据。

1
input = hiveCtx.jsonFile(jsonfile)

基于RDD

除了读取数据,我们也可以基于RDD创建DataFrame。Python中可以创建一个Row对象组成的RDD,然后调用inferSchema()。

1
2
3
RDDn = sc.parallelize([Row(name="zy",tel="186")])
SchemaRDDn = hiveCtx.inferSchema(RDDn)
SchemaRDDn.registerTempTable("info")

UDF

SparkSQL不仅有自己的UDF接口,也支持Hive的UDF接口。

SparkSQL UDF

我们可以使用Spark支持的内建语言编写好函数,然后通过SparkSQL内建的方法传递进来,配合Python/Scala很好用。

Hive UDF

标准的Hive UDF已经自动添加在SparkSQL中,如果需要添加用户自定义的UDF,我们就需要确保该UDF的JAR包已经在应用中。

SparkSQL性能调优

这个估计是要单写一篇文章讲的吧…

Spark学习笔记之运行流程

Posted on 2019-02-28

感恩博主扎心了,老铁

Spark中的基本概念

  • Application: 表示某个应用程序
  • Driver: main()函数,创建SparkContext。由SparkContext与ClusterManager通信,进行资源的申请,任务的分配和监控等。在程序执行完毕之后关闭SparkContext。
  • Excuter: 某个Application运行在一个节点的一个进程,负责运行某些Task,并将数据存在内存或者磁盘上。在Spark on Yarn模式下,其进程名称为 CoarseGrainedExecutor Backend,一个CoarseGrainedExecutor Backend进程有且仅有一个executor对象,它负责将Task包装成taskRunner,并从线程池中抽取出一个空闲线程运行Task,这样,每个CoarseGrainedExecutorBackend能并行运行Task的数据就取决于分配给它的CPU的个数。
  • Worker: 集群中可以运行Application的节点,在Spark on Yarn模式下指DataNode。
  • Task: task是被提交到executor上的work单元,每一个stage都有一些task,一个task对应一个partition。
  • Job: Job是由一系列tasks组成的并行计算任务,它是由action类型的函数激活的,换言之没有action,job不会被提交。
  • Stage: 每一个job都被划分为几个小的set,这些set被称之为stages,它们之间是相互依赖的。
  • DAGScheduler: 根据Job构建基于Stage的DAG,并提交Stage给TaskScheduler,其划分Stage的依据是RDD之间的依赖关系
  • TaskScheduler: 将TaskSet提交给集群(worker)运行。

Spark的基本流程

Spark学习笔记之累加器与广播变量

Posted on 2019-02-28

概述

在我们与Spark的交互中,我们的操作在远程节点运行时,Spark操作的实际上是这个函数所用变量的一个独立副本。这些变量会被复制到每台机器上,并且这些变量在远程机器上所有更新不会被传递回驱动程序。同时,跨任务的读写变量是很低效的。

Spark为两种较为常见情况的提供了共享变量:广播变量和累加器

累加器

常见用途是在调试时对作业的执行过程中的事件进行计数

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
# 定义一个累加器
blackLines = sc.accumulator(0)


def extractlines(line):
global blackLines
if line == "\'\'\,":
blackLines += 1
return line.split(' ')

textRDD.flatMap(extractlines)
print('blackline %s'%(blackLines))

注意事项

  • 累加器在Driver端定义赋初始值,累加器只能在Driver端读取最后的值,在Excutor端更新。

广播变量

如果我们要在分布式计算里面分发大对象,例如:字典,集合,黑白名单等,这个都会由Driver端进行分发,一般来讲,如果这个变量不是广播变量,那么每个task就会分发一份,这在task数目十分多的情况下Driver的带宽会成为系统的瓶颈,而且会大量消耗task服务器上的资源,如果将这个变量声明为广播变量,那么这个值只会被发送到各节点一次,使用BitTorrent的通信机制。

1
2
3
4
5
6
7

# 定义一个广播变量
a = 'xxx'
broadcast = sc.broadcast(a)

# 还原一个广播变量
b = broadcast.value

变量一旦被定义为一个广播变量,那么这个变量只能读,不能修改

注意事项:

  • 不能将一个RDD使用广播变量广播出去,可以广播RDD的结果

  • 广播变量只能在Driver端定义,不能在Executor端定义。

  • 在Driver端可以修改广播变量的值,在Executor端无法修改广播变量的值。

  • 果executor端用到了Driver的变量,如果不使用广播变量在Executor有多少task就有多少Driver端的变量副本。

  • 如果Executor端用到了Driver的变量,如果使用广播变量在每个Executor中只有一份Driver端的变量副本。

Spark学习笔记之RDD依赖

Posted on 2019-02-26

依赖关系

RDD是一个粗粒度的数据集,什么都可以往里面放,而且前面我们知道了转化操作会生成新的RDD,由此形成了RDD之间就形成了前后依赖关系,RDD和它依赖的父RDD(s)的关系有两种不同的类型,即窄依赖(narrow dependency)和宽依赖(wide dependency),如下图。

RDD依赖

从图中可知:

窄依赖:指每个父RDD的一个Partition最多被一个子RDD的Partition所使用,比如filter,union,map等操作都会产生窄依赖,也有说法是‘独生子女’

宽依赖:指一个父RDD的一个Partition被多个子RDD的Partition所使用,比如groupByKey(),reduceByKey(),sortByKey()等都会产生宽依赖,超生。

对于join操作有两种情况:

  • 如果两个RDD在进行join操作时,一个RDD的partition仅仅和另一个RDD中已知个数的Partition进行join,那么这种类型的join操作就是窄依赖,如图中左半部分的join操作
  • 其它情况的join操作就是宽依赖,由于是需要父RDD的所有partition进行join的转换,这就涉及到了shuffle,因此这种类型的join操作也是宽依赖

总结一下:

我们使用父RDD的Partition被子RDD的Partition所使用的个数来决定依赖关系,即:如果父RDD的一个Partition被子RDD的一个Partition所使用就是窄依赖,否则的话就是宽依赖。同时一个子RDD的Partition可以依赖多个父RDD的Partition,只要关系数量确定,也为窄依赖。
具体解释为:即子RDD的partition对父RDD依赖的Partition的数量不会随着RDD数据规模的改变而改变。即无论是有100T的数据量还是1P的数据量,在窄依赖中,子RDD所依赖的父RDD的partition的个数是确定的,而宽依赖是shuffle级别的,数据量越大,那么子RDD所依赖的父RDD的个数就越多,从而子RDD所依赖的父RDD的partition的个数也会变得越来越多。

数据流图

Spark根据RDD之间的依赖关系来为计算任务生成不同的阶段。由于窄依赖间的依赖关系是确定的,其转换处理可以在同一个线程里完成,因此窄依赖会被Spark划分到同一个Stage里面;宽依赖则只能等父RDD的shuffle流程处理完之后,在下一个stage才能开始接下来的计算。

RDD数据流

因此Spark划分stage的思路是:从后往前推,遇到宽依赖就断开划分为一个新的Stage,遇到窄依赖就将这个RDD加入该stage中。

因此如图所示,A为一个单独的Stage,CDEF均为窄依赖,为一个stage,BF为一个stage

Spark学习笔记之RDD

Posted on 2019-02-20

什么是RDD

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。RDD具有数据流模型的特点:

  • 自动容错
  • 位置感知性调度
  • 可伸缩性

RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度(持久化)。

RDD核心概念

首先我们需要初始化Spark的上下文环境,Spark2.4.0版本应该是默认初始化了的。

1
2
from pyspark import SparkConf, SparkContext
sc = SparkContext()

然后再创建RDD

1
2
3
4
5
intRDD = sc.parallelize([1,2,3,4,5,6])
intRDD.collect()

stringRDD = sc.parallelize(['zhang','yang'])
stringRDD.collect()

转化运算

转化运算是惰性的

RDD基本转化运算

map运算

1
intRDD.map(lambda x:x+1).collect()
[2, 3, 4, 5, 6, 7, 3, 4]
1
intRDD.map(lambda x:x**2).collect()
[1, 4, 9, 16, 25, 36, 4, 9]
1
2
lines = sc.textFile('README.md')
lines.first()
'[![buildstatus](https://travis-ci.org/holdenk/learning-spark-examples.svg?branch=master)](https://travis-ci.org/holdenk/learning-spark-examples)'

filter运算

1
2
3
4
5
6
pairs = lines.map(lambda x:(x.split(' ')[0],x))

#对第二个元素进行筛选

result = pairs.filter(lambda keyValue:len(keyValue[1])<20)
result.collect()[0]
('===============', '===============')

distinct运算

1
intRDD.distinct().collect()
[4, 1, 5, 2, 6, 3]

randomsplit运算

1
2
3
4
sRDD = intRDD.randomSplit([0.1,0.1])
print(len(sRDD))
print(sRDD[0].collect())
print(sRDD[1].collect())
2
[3, 4, 5, 2, 3]
[1, 2, 6]

groupby运算

1
2
3
result = intRDD.groupBy(lambda x : x % 2).collect()
for x,y in result:
print(x,sorted(y))
0 [2, 2, 4, 6]
1 [1, 3, 3, 5]

多个RDD转化运算

1
2
3
intRDD1 = sc.parallelize([3,1,2,5,5])
intRDD2 = sc.parallelize([5,6,2])
intRDD3 = sc.parallelize([2,7])

取并集

1
2
3
# union()
print(intRDD1.union(intRDD2).union(intRDD3).collect()) #取并集
print(intRDD1.union(intRDD2).union(intRDD3).distinct().collect()) #并集且去重
[3, 1, 2, 5, 5, 5, 6, 2, 2, 7]
[1, 2, 3, 5, 6, 7]

取交集

1
2
# intersection()
print(intRDD1.intersection(intRDD2).intersection(intRDD3).collect())
[2]

取差集

1
2
# subtract()
print(intRDD1.subtract(intRDD2).collect())
[1, 3]

笛卡尔积运算 cartesian()

1
2
# cartsection()
print(intRDD1.cartesian(intRDD2).collect())
[(3, 5), (3, 6), (3, 2), (1, 5), (1, 6), (1, 2), (2, 5), (2, 6), (2, 2), (5, 5), (5, 5), (5, 6), (5, 6), (5, 2), (5, 2)]

动作运算

基本动作运算

读取元素

1
2
3
4
5
6
7
8
#取第一条数据
print (intRDD.first())
#取前两条数据
print (intRDD.take(2))
#升序排列,并取前3条数据
print (intRDD.takeOrdered(3))
#降序排列,并取前3条数据
print (intRDD.takeOrdered(3,lambda x:-x))
1
[1, 2]
[1, 2, 2]
[6, 5, 4]

统计值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#统计
print (intRDD.stats())
#最小值
print (intRDD.min())
#最大值
print (intRDD.max())
#标准差
print (intRDD.stdev())
#计数
print (intRDD.count())
#求和
print (intRDD.sum())
#平均
print (intRDD.mean())
(count: 8, mean: 3.25, stdev: 1.5612494995995996, max: 6.0, min: 1.0)
1
6
1.5612494995995996
8
26
3.25

k-v转换

1
kvRDD1 = sc.parallelize([(3,4),(3,6),(5,6),(1,2)])
1
print(kvRDD1.keys().collect())
[3, 3, 5, 1]
1
print(kvRDD1.values().collect())
[4, 6, 6, 2]

筛选元素

1
print (kvRDD1.filter(lambda x:x[0] < 5).collect())
[(3, 4), (3, 6), (1, 2)]

值运算

使用mapvalue()进行排序

1
print(kvRDD1.mapValues(lambda x:x**2).collect())
[(3, 16), (3, 36), (5, 36), (1, 4)]

按照key排序

sortByKey(),传入的参数默认为True,表示从小到大排序

1
print(kvRDD1.sortByKey().collect())
[(1, 2), (3, 4), (3, 6), (5, 6)]
1
print(kvRDD1.sortByKey(False).collect())
[(5, 6), (3, 4), (3, 6), (1, 2)]

合并相同key值的数据

reduceByKey()

1
print(kvRDD1.reduceByKey(lambda x,y:x+y).collect())
[(5, 6), (1, 2), (3, 10)]

多个RDD kv转化运算

1
2
kvRDD1 = sc.parallelize([(3,4),(3,6),(5,6),(1,2)])
kvRDD2 = sc.parallelize([(3,8)])

内连接

1
print(kvRDD1.join(kvRDD2).collect())
[(3, (4, 8)), (3, (6, 8))]

左外连接

1
print(kvRDD1.leftOuterJoin(kvRDD2).collect())
[(1, (2, None)), (3, (4, 8)), (3, (6, 8)), (5, (6, None))]

右外连接

1
print(kvRDD1.rightOuterJoin(kvRDD2).collect())
[(3, (4, 8)), (3, (6, 8))]

删除相同key值数据

1
print(kvRDD1.subtractByKey(kvRDD2).collect())
[(1, 2), (5, 6)]

kv-动作运算

读取数据

1
2
3
4
5
6
7
8
#读取第一条数据
print(kvRDD1.first())
#读取前两条数据
print(kvRDD1.take(2))
#读取第一条数据的key值
print(kvRDD1.first()[0])
#读取第一条数据的value值
print(kvRDD1.first()[1])
(3, 4)
[(3, 4), (3, 6)]
3
4

按key值统计

1
print(kvRDD1.countByKey())
defaultdict(<class 'int'>, {3: 2, 5: 1, 1: 1})

lookup查找

1
print(kvRDD1.lookup(3))
[4, 6]

持久化操作

spark RDD的持久化机制,可以将重复运算的RDD存储在内存中,可以大幅提升运算效率

持久化

1
kvRDD1.persist()
ParallelCollectionRDD[24] at parallelize at PythonRDD.scala:195
等级 说明
MEMORY_ONLY 以反序列化的JAVA对象的方式存储在JVM中. 如果内存不够, RDD的一些分区将不会被缓存, 这样当再次需要这些分区的时候,将会重新计算。这是默认的级别。
MEMORY_AND_DISK 以反序列化的JAVA对象的方式存储在JVM中. 如果内存不够, RDD的一些分区将将会缓存在磁盘上,再次需要的时候从磁盘读取。
MEMORY_ONLY_SER 以序列化JAVA对象的方式存储 (每个分区一个字节数组). 相比于反序列化的方式,这样更高效的利用空间, 尤其是使用快速序列化时。但是读取是CPU操作很密集。
MEMORY_AND_DISK_SER 与MEMORY_ONLY_SER相似, 区别是但内存不足时,存储在磁盘上而不是每次重新计算。
DISK_ONLY 只存储RDD在磁盘
MEMORY_ONLY_2, MEMORY_AND_DISK_2, etc. 与上面的级别相同,只不过每个分区的副本只存储在两个集群节点上。
OFF_HEAP (experimental) 将RDD以序列化的方式存储在 Tachyon. 与 MEMORY_ONLY_SER相比, OFF_HEAP减少了垃圾回收。允许执行体更小通过共享一个内存池。因此对于拥有较大堆内存和高并发的环境有较大的吸引力。更重要的是,因为RDD存储在Tachyon上,执行体的崩溃不会造成缓存的丢失。在这种模式下.Tachyon中的内存是可丢弃的,这样 Tachyon 对于从内存中挤出的块不会试图重建它。如果你打算使用Tachyon作为堆缓存,Spark提供了与Tachyon相兼容的版本。
1
2
3
4
5
6
7
8
9

#pyspark中封装了StorageLevel类,实现不同存储等级的存储
from pyspark.storagelevel import StorageLevel
def __init__(self, useDisk, useMemory, useOffHeap, deserialized, replication=1):
self.useDisk = useDisk
self.useMemory = useMemory
self.useOffHeap = useOffHeap
self.deserialized = deserialized
self.replication = replication

取消持久化

1
kvRDD1.unpersist()
ParallelCollectionRDD[24] at parallelize at PythonRDD.scala:195

Spark学习笔记00

Posted on 2019-02-20

前提是提前安装好Java,因为之前鼓捣Hadoop的时候已经搞定,所以安装很方便,一行命令搞定。

1
brew install apache-spark

配合Jupyter使用:

1
2
export PYSPARK_DRIVER_PYTHON=jupyter
export PYSPARK_DRIVER_PYTHON_OPTS='notebook'

相关链接:
brew-spark
官网

入坑tmux

Posted on 2019-02-02

写在最前

18年3月刚来贵司实习的时候,jy就推荐了一些shell方面好用的工具,比如Iterm2,zsh,tmux之类的,还有14pt Source Code Pro Semibold Italic这款很好看的字体,autojump,the_sliver_seracher等神器。除了tmux,最开始体验的时候那反人类的复制滚屏模式劝退了我,其他用得不要太爽呀~

前几天觉得工作上需要在shell里面开的窗口越来越多了,得从工具上来一波更新,于是实战了一下tmux,这次感觉好多了,记录分享一下。

Tmux

tmux主要优点有下面三个:

  • 强大的分屏支持: tmux窗口中,新开的pane,默认进入到之前的路径,如果是ssh连接,登录状态也依旧保持
  • 保护现场 : 下班回家连上服务器就可以很快回到操作现场,简洁省事
  • 会话共享 : 将tmux会话的地址分享给他人,这样他们就可以通过 SSH 接入该会话(我应该是用不到这个了)

安装tmux

在Mac上安装

1
2
3
4
# 先安装Homebrew,有则跳过
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
# 安装tmux
brew install tmux

在Linux上安装

1
sudo apt-get install tmux

基本概念

session-window-pane:一个session有多个窗口,一个窗口有多个面板

多任务神器,用过就知道

直达使用

我们先创建一个名为local的session,用第一个命令即可

1
2
3
tmux new -s local # 创建一个名为local的session
tmux kill-session -t local # 关闭绘画
tmux kill-server #关闭服务器,所有会话被关闭

此时我们可以看到底部默认的小绿条,我理解这就是tmux的状态栏了嘛。tmux已经默认为我们创建好了一个window,这个时候就可以去执行切分pane之类的操作了。我这里随便举个栗子:

1
2
3
4
5
6
7
# 默认所有操作都加了前缀,tmux的快捷键也都是加了前缀才会生效
, # 重命名当前window
- # 横切窗口,默认进入当前目录
| # 竖切窗口,默认进入当前目录
c # 新建window
hjkl # 切换窗口,类似vim操作
t # 显示时钟

我随便截图一下表示效果

我的Tmux

tmux设置

这里直接附上我的.tmux.conf文件,不过基本也是来自网上大佬的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#设置前缀为Ctrl + x
set -g prefix C-x

#解除Ctrl+b 与前缀的对应关系
unbind C-b

#up
bind-key k select-pane -U
#down
bind-key j select-pane -D
#left
bind-key h select-pane -L
#right
bind-key l select-pane -R

# 绑定快捷键为r
bind r source-file ~/.tmux.conf \; display-message "Config reloaded.."

unbind '"'
bind - splitw -v -c '#{pane_current_path}' # 垂直方向新增面板,默认进入当前目录
unbind %
bind | splitw -h -c '#{pane_current_path}' # 水平方向新增面板,默认进入当前目录

# 绑定Ctrl+hjkl键为面板上下左右调整边缘的快捷指令
bind -r ^k resizep -U 5 # 绑定Ctrl+k为往↑调整面板边缘10个单元格
bind -r ^j resizep -D 5 # 绑定Ctrl+j为往↓调整面板边缘10个单元格
bind -r ^h resizep -L 5 # 绑定Ctrl+h为往←调整面板边缘10个单元格
bind -r ^l resizep -R 5 # 绑定Ctrl+l为往→调整面板边缘10个单元格

# 开启鼠标
set-option -g mouse on

这里要感恩网上大佬分享的教程Tmux使用手册

写在最后

复制滚屏模式直到现在都还有点不习惯,不过还好开启了鼠标支持,后面再来吐槽。

utf8编码坑

Posted on 2019-02-01

保存文件是出现乱码

文件缺失BOM的锅,用utf_8_sig解决…具体编码怎么回事参见BOM

1
df.to_csv("file",encoding="utf_8_sig")

mysql-utf8mb4

MySQL的字符设置….注意一下

1…678…12
zhangyang

zhangyang

120 posts
39 tags
© 2022 zhangyang
Powered by Hexo
|
Theme — NexT.Mist v5.1.4