-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.json
141 lines (141 loc) · 107 KB
/
index.json
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
[
{
"uri": "/5-learn/data_struct/",
"title": "核心数据结构",
"tags": [],
"description": "",
"content": " 简介 可以阅读一下 程序员面试经典(第6版), 其中第 7 章是可以公开阅读的, 第 7 章 技术面试题.\n里面介绍了一下解面试题的技巧, 但第一步是熟练掌握核心的数据结构算法和概念.\n 数据结构 算法 概念 链表 广度优先搜索 位操作 树、单词查找树、图 深度优先搜索 内存(堆和栈) 栈和队列 二分查找 递归 堆 归并排序 动态规划 向量/数组列表 快排 大 O 时间及空间 散列表 只记录最基础常见的内容, Python 实现.\n数据结构 链表 链表是一种线性表, 通过在每个节点上存储下一个节点的指针, 来实现对整个链表的访问.\n链表插入删除可以达到 O(1), 查找需要 O(n).\nclass Node: def __init__(self, item, next): self.item = item self.next = next class LinkedList: def __init__(self): self.head = None def add(self, item): self.head = Node(item, self.head) def remove(self): if self.is_empty(): return None else: item = self.head.item self.head = self.head.next return item def is_empty(self): return self.head is None 树 二叉树 二叉树是每个节点最多只有两个子节点的树. 完全二叉树是二叉树的特例, 除了最后一层, 其余层都是满的, 且最后一层要么是满的, 要么只在右边缺少连续若干个节点. 满二叉树是所有层都是满的二叉树.\nclass Node: # This is the Class Node with constructor that contains data variable to type data and left,right pointers. def __init__(self, data): self.data = data self.left = None self.right = None def display(tree): # In Order traversal of the tree if tree is None: return if tree.left is not None: display(tree.left) print(tree.data) if tree.right is not None: display(tree.right) return def depth_of_tree(tree): # This is the recursive function to find the depth of binary tree. if tree is None: return 0 else: depth_l_tree = depth_of_tree(tree.left) depth_r_tree = depth_of_tree(tree.right) if depth_l_tree \u0026gt; depth_r_tree: return 1 + depth_l_tree else: return 1 + depth_r_tree def is_full_binary_tree(tree): # This functions returns that is it full binary tree or not? if tree is None: return True if (tree.left is None) and (tree.right is None): return True if (tree.left is not None) and (tree.right is not None): return is_full_binary_tree(tree.left) and is_full_binary_tree(tree.right) else: return False 二叉树的遍历 访问二叉树可以分解为 3 个步骤, 访问左子树, 访问根节点, 访问右子树. 分别用 LDR 表示, 根据根节点的访问顺序, 可以分为\n 前序遍历 DLR 中序遍历 LDR 后序遍历 LRD 深度优先遍历是指从根节点出发, 优先访问最远的节点, 前中后序遍历都是深度优先遍历的特例.\n广度优先遍历时指优先访问离根节点最近的节点, 又称为层次遍历, 通常借助队列实现.\n\u0026quot;\u0026quot;\u0026quot; This is pure python implementation of tree traversal algorithms \u0026quot;\u0026quot;\u0026quot; import queue from typing import List class TreeNode: def __init__(self, data): self.data = data self.right = None self.left = None def pre_order(node: TreeNode) -\u0026gt; None: \u0026quot;\u0026quot;\u0026quot; \u0026gt;\u0026gt;\u0026gt; root = TreeNode(1) \u0026gt;\u0026gt;\u0026gt; tree_node2 = TreeNode(2) \u0026gt;\u0026gt;\u0026gt; tree_node3 = TreeNode(3) \u0026gt;\u0026gt;\u0026gt; tree_node4 = TreeNode(4) \u0026gt;\u0026gt;\u0026gt; tree_node5 = TreeNode(5) \u0026gt;\u0026gt;\u0026gt; tree_node6 = TreeNode(6) \u0026gt;\u0026gt;\u0026gt; tree_node7 = TreeNode(7) \u0026gt;\u0026gt;\u0026gt; root.left, root.right = tree_node2, tree_node3 \u0026gt;\u0026gt;\u0026gt; tree_node2.left, tree_node2.right = tree_node4 , tree_node5 \u0026gt;\u0026gt;\u0026gt; tree_node3.left, tree_node3.right = tree_node6 , tree_node7 \u0026gt;\u0026gt;\u0026gt; pre_order(root) 1 2 4 5 3 6 7 \u0026quot;\u0026quot;\u0026quot; if not isinstance(node, TreeNode) or not node: return print(node.data, end=\u0026quot; \u0026quot;) pre_order(node.left) pre_order(node.right) def in_order(node: TreeNode) -\u0026gt; None: \u0026quot;\u0026quot;\u0026quot; \u0026gt;\u0026gt;\u0026gt; root = TreeNode(1) \u0026gt;\u0026gt;\u0026gt; tree_node2 = TreeNode(2) \u0026gt;\u0026gt;\u0026gt; tree_node3 = TreeNode(3) \u0026gt;\u0026gt;\u0026gt; tree_node4 = TreeNode(4) \u0026gt;\u0026gt;\u0026gt; tree_node5 = TreeNode(5) \u0026gt;\u0026gt;\u0026gt; tree_node6 = TreeNode(6) \u0026gt;\u0026gt;\u0026gt; tree_node7 = TreeNode(7) \u0026gt;\u0026gt;\u0026gt; root.left, root.right = tree_node2, tree_node3 \u0026gt;\u0026gt;\u0026gt; tree_node2.left, tree_node2.right = tree_node4 , tree_node5 \u0026gt;\u0026gt;\u0026gt; tree_node3.left, tree_node3.right = tree_node6 , tree_node7 \u0026gt;\u0026gt;\u0026gt; in_order(root) 4 2 5 1 6 3 7 \u0026quot;\u0026quot;\u0026quot; if not isinstance(node, TreeNode) or not node: return in_order(node.left) print(node.data, end=\u0026quot; \u0026quot;) in_order(node.right) def post_order(node: TreeNode) -\u0026gt; None: \u0026quot;\u0026quot;\u0026quot; \u0026gt;\u0026gt;\u0026gt; root = TreeNode(1) \u0026gt;\u0026gt;\u0026gt; tree_node2 = TreeNode(2) \u0026gt;\u0026gt;\u0026gt; tree_node3 = TreeNode(3) \u0026gt;\u0026gt;\u0026gt; tree_node4 = TreeNode(4) \u0026gt;\u0026gt;\u0026gt; tree_node5 = TreeNode(5) \u0026gt;\u0026gt;\u0026gt; tree_node6 = TreeNode(6) \u0026gt;\u0026gt;\u0026gt; tree_node7 = TreeNode(7) \u0026gt;\u0026gt;\u0026gt; root.left, root.right = tree_node2, tree_node3 \u0026gt;\u0026gt;\u0026gt; tree_node2.left, tree_node2.right = tree_node4 , tree_node5 \u0026gt;\u0026gt;\u0026gt; tree_node3.left, tree_node3.right = tree_node6 , tree_node7 \u0026gt;\u0026gt;\u0026gt; post_order(root) 4 5 2 6 7 3 1 \u0026quot;\u0026quot;\u0026quot; if not isinstance(node, TreeNode) or not node: return post_order(node.left) post_order(node.right) print(node.data, end=\u0026quot; \u0026quot;) def level_order(node: TreeNode) -\u0026gt; None: \u0026quot;\u0026quot;\u0026quot; \u0026gt;\u0026gt;\u0026gt; root = TreeNode(1) \u0026gt;\u0026gt;\u0026gt; tree_node2 = TreeNode(2) \u0026gt;\u0026gt;\u0026gt; tree_node3 = TreeNode(3) \u0026gt;\u0026gt;\u0026gt; tree_node4 = TreeNode(4) \u0026gt;\u0026gt;\u0026gt; tree_node5 = TreeNode(5) \u0026gt;\u0026gt;\u0026gt; tree_node6 = TreeNode(6) \u0026gt;\u0026gt;\u0026gt; tree_node7 = TreeNode(7) \u0026gt;\u0026gt;\u0026gt; root.left, root.right = tree_node2, tree_node3 \u0026gt;\u0026gt;\u0026gt; tree_node2.left, tree_node2.right = tree_node4 , tree_node5 \u0026gt;\u0026gt;\u0026gt; tree_node3.left, tree_node3.right = tree_node6 , tree_node7 \u0026gt;\u0026gt;\u0026gt; level_order(root) 1 2 3 4 5 6 7 \u0026quot;\u0026quot;\u0026quot; if not isinstance(node, TreeNode) or not node: return q: queue.Queue = queue.Queue() q.put(node) while not q.empty(): node_dequeued = q.get() print(node_dequeued.data, end=\u0026quot; \u0026quot;) if node_dequeued.left: q.put(node_dequeued.left) if node_dequeued.right: q.put(node_dequeued.right) # iteration version def pre_order_iter(node: TreeNode) -\u0026gt; None: \u0026quot;\u0026quot;\u0026quot; \u0026gt;\u0026gt;\u0026gt; root = TreeNode(1) \u0026gt;\u0026gt;\u0026gt; tree_node2 = TreeNode(2) \u0026gt;\u0026gt;\u0026gt; tree_node3 = TreeNode(3) \u0026gt;\u0026gt;\u0026gt; tree_node4 = TreeNode(4) \u0026gt;\u0026gt;\u0026gt; tree_node5 = TreeNode(5) \u0026gt;\u0026gt;\u0026gt; tree_node6 = TreeNode(6) \u0026gt;\u0026gt;\u0026gt; tree_node7 = TreeNode(7) \u0026gt;\u0026gt;\u0026gt; root.left, root.right = tree_node2, tree_node3 \u0026gt;\u0026gt;\u0026gt; tree_node2.left, tree_node2.right = tree_node4 , tree_node5 \u0026gt;\u0026gt;\u0026gt; tree_node3.left, tree_node3.right = tree_node6 , tree_node7 \u0026gt;\u0026gt;\u0026gt; pre_order_iter(root) 1 2 4 5 3 6 7 \u0026quot;\u0026quot;\u0026quot; if not isinstance(node, TreeNode) or not node: return stack: List[TreeNode] = [] n = node while n or stack: while n: # start from root node, find its left child print(n.data, end=\u0026quot; \u0026quot;) stack.append(n) n = n.left # end of while means current node doesn't have left child n = stack.pop() # start to traverse its right child n = n.right def in_order_iter(node: TreeNode) -\u0026gt; None: \u0026quot;\u0026quot;\u0026quot; \u0026gt;\u0026gt;\u0026gt; root = TreeNode(1) \u0026gt;\u0026gt;\u0026gt; tree_node2 = TreeNode(2) \u0026gt;\u0026gt;\u0026gt; tree_node3 = TreeNode(3) \u0026gt;\u0026gt;\u0026gt; tree_node4 = TreeNode(4) \u0026gt;\u0026gt;\u0026gt; tree_node5 = TreeNode(5) \u0026gt;\u0026gt;\u0026gt; tree_node6 = TreeNode(6) \u0026gt;\u0026gt;\u0026gt; tree_node7 = TreeNode(7) \u0026gt;\u0026gt;\u0026gt; root.left, root.right = tree_node2, tree_node3 \u0026gt;\u0026gt;\u0026gt; tree_node2.left, tree_node2.right = tree_node4 , tree_node5 \u0026gt;\u0026gt;\u0026gt; tree_node3.left, tree_node3.right = tree_node6 , tree_node7 \u0026gt;\u0026gt;\u0026gt; in_order_iter(root) 4 2 5 1 6 3 7 \u0026quot;\u0026quot;\u0026quot; if not isinstance(node, TreeNode) or not node: return stack: List[TreeNode] = [] n = node while n or stack: while n: stack.append(n) n = n.left n = stack.pop() print(n.data, end=\u0026quot; \u0026quot;) n = n.right def post_order_iter(node: TreeNode) -\u0026gt; None: \u0026quot;\u0026quot;\u0026quot; \u0026gt;\u0026gt;\u0026gt; root = TreeNode(1) \u0026gt;\u0026gt;\u0026gt; tree_node2 = TreeNode(2) \u0026gt;\u0026gt;\u0026gt; tree_node3 = TreeNode(3) \u0026gt;\u0026gt;\u0026gt; tree_node4 = TreeNode(4) \u0026gt;\u0026gt;\u0026gt; tree_node5 = TreeNode(5) \u0026gt;\u0026gt;\u0026gt; tree_node6 = TreeNode(6) \u0026gt;\u0026gt;\u0026gt; tree_node7 = TreeNode(7) \u0026gt;\u0026gt;\u0026gt; root.left, root.right = tree_node2, tree_node3 \u0026gt;\u0026gt;\u0026gt; tree_node2.left, tree_node2.right = tree_node4 , tree_node5 \u0026gt;\u0026gt;\u0026gt; tree_node3.left, tree_node3.right = tree_node6 , tree_node7 \u0026gt;\u0026gt;\u0026gt; post_order_iter(root) 4 5 2 6 7 3 1 \u0026quot;\u0026quot;\u0026quot; if not isinstance(node, TreeNode) or not node: return stack1, stack2 = [], [] n = node stack1.append(n) while stack1: # to find the reversed order of post order, store it in stack2 n = stack1.pop() if n.left: stack1.append(n.left) if n.right: stack1.append(n.right) stack2.append(n) while stack2: # pop up from stack2 will be the post order print(stack2.pop().data, end=\u0026quot; \u0026quot;) 单词查找树 单词查找树, trie, 又叫做前缀树或字典树. 常用于搜索提示.\n\u0026quot;\u0026quot;\u0026quot; A Trie/Prefix Tree is a kind of search tree used to provide quick lookup of words/patterns in a set of words. A basic Trie however has O(n^2) space complexity making it impractical in practice. It however provides O(max(search_string, length of longest word)) lookup time making it an optimal approach when space is not an issue. \u0026quot;\u0026quot;\u0026quot; class TrieNode: def __init__(self): self.nodes = dict() # Mapping from char to TrieNode self.is_leaf = False # 叶节点是没有值的, 即 nodes 是空的 def insert_many(self, words: [str]): \u0026quot;\u0026quot;\u0026quot; Inserts a list of words into the Trie :param words: list of string words :return: None \u0026quot;\u0026quot;\u0026quot; for word in words: self.insert(word) def insert(self, word: str): \u0026quot;\u0026quot;\u0026quot; Inserts a word into the Trie :param word: word to be inserted :return: None \u0026quot;\u0026quot;\u0026quot; curr = self for char in word: if char not in curr.nodes: curr.nodes[char] = TrieNode() curr = curr.nodes[char] curr.is_leaf = True def find(self, word: str) -\u0026gt; bool: \u0026quot;\u0026quot;\u0026quot; Tries to find word in a Trie :param word: word to look for :return: Returns True if word is found, False otherwise \u0026quot;\u0026quot;\u0026quot; curr = self for char in word: if char not in curr.nodes: return False curr = curr.nodes[char] return curr.is_leaf def delete(self, word: str): \u0026quot;\u0026quot;\u0026quot; Deletes a word in a Trie :param word: word to delete :return: None \u0026quot;\u0026quot;\u0026quot; def _delete(curr: TrieNode, word: str, index: int): if index == len(word): # If word does not exist if not curr.is_leaf: return False curr.is_leaf = False return len(curr.nodes) == 0 char = word[index] char_node = curr.nodes.get(char) # If char not in current trie node if not char_node: return False # Flag to check if node can be deleted delete_curr = _delete(char_node, word, index + 1) if delete_curr: del curr.nodes[char] return len(curr.nodes) == 0 return delete_curr _delete(self, word, 0) 栈和队列 栈是一种线性数据结构, 以后进先出 LIFO 的原理运作.\n队列是一种线性数据结构, 以先进先出 FIFO 的原理运作.\n堆 堆是一种特殊的树结构, 给定堆中的任意节点 P 和 C, 如果 P 是 C 的祖先节点, 那么 P 的值总数小于等于(或大于等于) C 的值.\n堆根据判断条件, 可以分为最小堆和最大堆.\n堆通常用于事件模拟, 可以实现排序即堆排序.\n# This heap class start from here. class Heap: def __init__(self): # Default constructor of heap class. self.h = [] self.currsize = 0 def leftChild(self, i): if 2 * i + 1 \u0026lt; self.currsize: return 2 * i + 1 return None def rightChild(self, i): if 2 * i + 2 \u0026lt; self.currsize: return 2 * i + 2 return None def maxHeapify(self, node): if node \u0026lt; self.currsize: m = node lc = self.leftChild(node) rc = self.rightChild(node) if lc is not None and self.h[lc] \u0026gt; self.h[m]: m = lc if rc is not None and self.h[rc] \u0026gt; self.h[m]: m = rc if m != node: temp = self.h[node] self.h[node] = self.h[m] self.h[m] = temp self.maxHeapify(m) def buildHeap(self, a): # This function is used to build the heap from the data container 'a'. self.currsize = len(a) self.h = list(a) for i in range(self.currsize // 2, -1, -1): self.maxHeapify(i) def getMax(self): # This function is used to get maximum value from the heap. if self.currsize \u0026gt;= 1: me = self.h[0] temp = self.h[0] self.h[0] = self.h[self.currsize - 1] self.h[self.currsize - 1] = temp self.currsize -= 1 self.maxHeapify(0) return me return None def heapSort(self): # This function is used to sort the heap. size = self.currsize while self.currsize - 1 \u0026gt;= 0: temp = self.h[0] self.h[0] = self.h[self.currsize - 1] self.h[self.currsize - 1] = temp self.currsize -= 1 self.maxHeapify(0) self.currsize = size def insert(self, data): # This function is used to insert data in the heap. self.h.append(data) curr = self.currsize self.currsize += 1 while self.h[curr] \u0026gt; self.h[curr / 2]: temp = self.h[curr / 2] self.h[curr / 2] = self.h[curr] self.h[curr] = temp curr = curr / 2 def display(self): # This function is used to print the heap. print(self.h) def main(): ll = list(map(int, \u0026quot;2 3 11\u0026quot;.split())) h = Heap() h.buildHeap(ll) h.heapSort() h.display() if __name__ == \u0026quot;__main__\u0026quot;: main() 散列表 散列表(哈希表)是根据键通过计算直接访问内存存储位置的数据结构. 这个映射函数叫做散列函数, 存储数据的数组叫做散列表.\nfrom number_theory.prime_numbers import next_prime class HashTable: \u0026quot;\u0026quot;\u0026quot; Basic Hash Table example with open addressing and linear probing \u0026quot;\u0026quot;\u0026quot; def __init__(self, size_table, charge_factor=None, lim_charge=None): self.size_table = size_table self.values = [None] * self.size_table self.lim_charge = 0.75 if lim_charge is None else lim_charge self.charge_factor = 1 if charge_factor is None else charge_factor self.__aux_list = [] self._keys = {} def keys(self): return self._keys def balanced_factor(self): return sum([1 for slot in self.values if slot is not None]) / (self.size_table * self.charge_factor) def hash_function(self, key): return key % self.size_table def _step_by_step(self, step_ord): print(\u0026quot;step {0}\u0026quot;.format(step_ord)) print([i for i in range(len(self.values))]) print(self.values) def bulk_insert(self, values): i = 1 self.__aux_list = values for value in values: self.insert_data(value) self._step_by_step(i) i += 1 def _set_value(self, key, data): self.values[key] = data self._keys[key] = data def _colision_resolution(self, key, data=None): new_key = self.hash_function(key + 1) while self.values[new_key] is not None and self.values[new_key] != key: if self.values.count(None) \u0026gt; 0: new_key = self.hash_function(new_key + 1) else: new_key = None break return new_key def rehashing(self): survivor_values = [value for value in self.values if value is not None] self.size_table = next_prime(self.size_table, factor=2) self._keys.clear() self.values = [None] * self.size_table # hell's pointers D: don't DRY ;/ map(self.insert_data, survivor_values) def insert_data(self, data): key = self.hash_function(data) if self.values[key] is None: self._set_value(key, data) elif self.values[key] == data: pass else: colision_resolution = self._colision_resolution(key, data) if colision_resolution is not None: self._set_value(colision_resolution, data) else: self.rehashing() self.insert_data(data) 算法 二分查找 二分查找是在有序数组章查找某一个元素的搜索算法.\n时间复杂度是 O(log n).\n\u0026quot;\u0026quot;\u0026quot; This is pure python implementation of binary search algorithm For doctests run following command: python -m doctest -v binary_search.py or python3 -m doctest -v binary_search.py For manual testing run: python binary_search.py \u0026quot;\u0026quot;\u0026quot; import bisect def binary_search(sorted_collection, item): \u0026quot;\u0026quot;\u0026quot;Pure implementation of binary search algorithm in Python Be careful collection must be ascending sorted, otherwise result will be unpredictable :param sorted_collection: some ascending sorted collection with comparable items :param item: item value to search :return: index of found item or None if item is not found Examples: \u0026gt;\u0026gt;\u0026gt; binary_search([0, 5, 7, 10, 15], 0) 0 \u0026gt;\u0026gt;\u0026gt; binary_search([0, 5, 7, 10, 15], 15) 4 \u0026gt;\u0026gt;\u0026gt; binary_search([0, 5, 7, 10, 15], 5) 1 \u0026gt;\u0026gt;\u0026gt; binary_search([0, 5, 7, 10, 15], 6) \u0026quot;\u0026quot;\u0026quot; left = 0 right = len(sorted_collection) - 1 while left \u0026lt;= right: midpoint = left + (right - left) // 2 current_item = sorted_collection[midpoint] if current_item == item: return midpoint elif item \u0026lt; current_item: right = midpoint - 1 else: left = midpoint + 1 return None def binary_search_std_lib(sorted_collection, item): \u0026quot;\u0026quot;\u0026quot;Pure implementation of binary search algorithm in Python using stdlib Be careful collection must be ascending sorted, otherwise result will be unpredictable :param sorted_collection: some ascending sorted collection with comparable items :param item: item value to search :return: index of found item or None if item is not found Examples: \u0026gt;\u0026gt;\u0026gt; binary_search_std_lib([0, 5, 7, 10, 15], 0) 0 \u0026gt;\u0026gt;\u0026gt; binary_search_std_lib([0, 5, 7, 10, 15], 15) 4 \u0026gt;\u0026gt;\u0026gt; binary_search_std_lib([0, 5, 7, 10, 15], 5) 1 \u0026gt;\u0026gt;\u0026gt; binary_search_std_lib([0, 5, 7, 10, 15], 6) \u0026quot;\u0026quot;\u0026quot; index = bisect.bisect_left(sorted_collection, item) if index != len(sorted_collection) and sorted_collection[index] == item: return index return None def binary_search_by_recursion(sorted_collection, item, left, right): \u0026quot;\u0026quot;\u0026quot;Pure implementation of binary search algorithm in Python by recursion Be careful collection must be ascending sorted, otherwise result will be unpredictable First recursion should be started with left=0 and right=(len(sorted_collection)-1) :param sorted_collection: some ascending sorted collection with comparable items :param item: item value to search :return: index of found item or None if item is not found Examples: \u0026gt;\u0026gt;\u0026gt; binary_search_std_lib([0, 5, 7, 10, 15], 0) 0 \u0026gt;\u0026gt;\u0026gt; binary_search_std_lib([0, 5, 7, 10, 15], 15) 4 \u0026gt;\u0026gt;\u0026gt; binary_search_std_lib([0, 5, 7, 10, 15], 5) 1 \u0026gt;\u0026gt;\u0026gt; binary_search_std_lib([0, 5, 7, 10, 15], 6) \u0026quot;\u0026quot;\u0026quot; if right \u0026lt; left: return None midpoint = left + (right - left) // 2 if sorted_collection[midpoint] == item: return midpoint elif sorted_collection[midpoint] \u0026gt; item: return binary_search_by_recursion(sorted_collection, item, left, midpoint - 1) else: return binary_search_by_recursion(sorted_collection, item, midpoint + 1, right) 快速排序 平均情况下时间复杂度是 O(n log n), 最坏的情况下是 O(n^2).\ndef quick_sort(collection): '''简单版, 需要额外的 O(n) 的空间''' length = len(collection) if length \u0026lt;= 1: return collection else: # Use the last element as the first pivot pivot = collection.pop() # Put elements greater than pivot in greater list # Put elements lesser than pivot in lesser list greater, lesser = [], [] for element in collection: if element \u0026gt; pivot: greater.append(element) else: lesser.append(element) return quick_sort(lesser) + [pivot] + quick_sort(greater) def partition(array, left, right, pivot_index): '''就地划分''' pivot_val = array[pivot_index] array[pivot_index], array[right] = array[right], array[pivot_index] index = left for i in range(left, right): if array[i] \u0026lt;= pivot_val: array[index], array[i] = array[i], array[index] index += 1 array[right], array[index] = array[index], array[right] return index def quick_sort_in_place(array, left, right): '''就地排序版快速排序''' if right \u0026gt; left: pivot_index = left new_pivot_index = partition(array, left, right, pivot_index) quick_sort_in_place(array, left, new_pivot_index - 1) quick_sort_in_place(array, new_pivot_index + 1, right) def quicksort(alist, first, last): '''就地排序版''' if first \u0026gt;= last: return mid_value = alist[first] low = first high = last while low \u0026lt; high: while low \u0026lt; high and alist[high] \u0026gt;= mid_value: high -= 1 alist[low] = alist[high] while low \u0026lt; high and alist[low] \u0026lt; mid_value: low += 1 alist[high] = alist[low] alist[low] = mid_value quicksort(alist, first, low - 1) quicksort(alist, low + 1, last) 参考 TheAlgorithms Python: 代码实现基本来自这里.\n 相关代码文件 basic_binary_tree.py (1 ko) binary_search.py (4 ko) hash.py (2 ko) heap.py (2 ko) linked_list.py (0 ko) quick_sort.py (1 ko) traversals.py (8 ko) trie.py (3 ko) "
},
{
"uri": "/4-go/go_module/",
"title": "使用 Go 模块",
"tags": [],
"description": "",
"content": " 简介 Go 终于要有自己的模块了, 以前只有包, 而模块是包的上一级.\n以下是阅读官网上的两篇文章的总结.\n https://blog.golang.org/using-go-modules https://blog.golang.org/migrating-to-go-modules 使用 Go 模块 一个模块是存储在文件树中的一系列的 Go 包的集合, 根目录有一个 go.mod 文件.\ngo.mod 文件定义了模块的 module path, 这也是用于根目录的导入路径. 还定义了 dependency requirements, 这是构建需要的外部模块.\n模块的常见操作 创建一个新的模块 在 $GOPATH/src 之外, 创建一个目录, 并添加文件 hello.go:\npackage hello func Hello() string { return \u0026quot;Hello, world.\u0026quot; } 添加一个测试文件, hello_test.go:\npackage hello import \u0026quot;testing\u0026quot; func TestHello(t *testing.T) { want := \u0026quot;Hello, world.\u0026quot; if got := Hello(); got != want { t.Errorf(\u0026quot;Hello() = %q, want %q\u0026quot;, got, want) } } 到这里, 该目录包含一个包, 但还不是一个模块, 因为还没有 go.mod 文件.\n如果在该目录下运行 go test:\n$ go test PASS ok _/home/gopher/hello 0.020s $ 最后一行总结了所有的包的测试. 因为我们在 $GOPATH 目录外, 也在任何模块之外, 所有 go 命令对当前目录不知道任何导入路径, 这会制造一个假的基于当前目录名的模块: _/home/gopher/hello.\n现在, 使用 go mod init 将该目录初始化为一个模块, 并重新运行 go test.\n$ go mod init example.com/hello go: creating new go.mod: module example.com/hello $ go test PASS ok example.com/hello 0.020s $ 这样, 就创建一个 Go 模块, 并运行了测试. 注意到, 模块名字已经变成了 example.com/hello.\ngo mod init 命令产生了一个 go.mod 的文件.\n$ cat go.mod module example.com/hello go 1.12 $ go.mod 文件只出现在模块的根目录. 在子目录中的包的导入路径为 模块路径 加上 子目录 路径. 如果我们创建了一个叫做 world 的子目录, 这个包会自动被识别为 模块 example.com/hello的一部分, 它的导入路径为 example.com/hello/world.\n添加一个依赖 Go 模块的主要动力是提高代码被其他开发者作为依赖的体验.\n更新 hello.go, 导入 rsc.io/quote 并使用它来实现 Hello 函数.\npackage hello import \u0026quot;rsc.io/quote\u0026quot; func Hello() string { return quote.Hello() } 重新运行测试:\n$ go test go: finding rsc.io/quote v1.5.2 go: downloading rsc.io/quote v1.5.2 go: extracting rsc.io/quote v1.5.2 go: finding rsc.io/sampler v1.3.0 go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c go: downloading rsc.io/sampler v1.3.0 go: extracting rsc.io/sampler v1.3.0 go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c PASS ok example.com/hello 0.023s $ go 命令使用在 go.mod 中列出的特定依赖来解析导入. 当它遇到任何不在 go.mod 中的 import 导入的包时, go 命名会自动查找包含这个包的模块, 并将它添加到 go.mod 文件中, 并使用最新的版本号.\n$ cat go.mod module example.com/hello go 1.12 require rsc.io/quote v1.5.2 $ 再次运行 go test 不会重复上面的过程, 因为 go.mod 是最新的, 而且下载的模块已经缓存在本地了, 在 $GOPATH/pkg/mod目录下.\n添加一个直接的依赖通常会带来一些间接的依赖, 命令 go list -m all 列出当前模块和它的所有依赖.\n$ go list -m all example.com/hello golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c rsc.io/quote v1.5.2 rsc.io/sampler v1.3.0 $ 通常第一行是主模块, 下面都是它的依赖, 按模块路径排序.\ngolang.org/x/text 的版本是 v0.0.0-20170915032832-14c0d48ead0c, 这是一个 伪版本(pseudo-version), 这是 go 命令对没有标签的提交的版本语法.\n除了 go.mod 之外, go 命令还维护了一个叫做 go.sum 的文件, 包含每一个特定版本的模块的内容的加密哈希.\n$ cat go.sum golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZO... golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:Nq... rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3... rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPX... rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/Q... rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9... $ go 命令通过使用 go.sum 文件来未来的下载和第一次下载一样, 有相同的 bit. 保证你的项目依赖的模块不会发生意外的变化. 这两个文件 go.mod 和 go.sum 都应该保存在版本控制之中.\n升级依赖 使用 Go 模块, 版本号使用语义版本标签. 一个语义版本有三个部分: 主版本, 次版本, 补丁版本.\n因为前面通过 go list -m all 看到 golang.org/x/text 使用了一个未标记的版本, 让我们升级到最新的版本, 并测试一切都正常.\n$ go get golang.org/x/text go: finding golang.org/x/text v0.3.0 go: downloading golang.org/x/text v0.3.0 go: extracting golang.org/x/text v0.3.0 $ go test PASS ok example.com/hello 0.013s $ 一切正常, 重新看看 go list -m al 的输出, 以及 go.mod 文件.\n$ go list -m all example.com/hello golang.org/x/text v0.3.0 rsc.io/quote v1.5.2 rsc.io/sampler v1.3.0 $ cat go.mod module example.com/hello go 1.12 require ( golang.org/x/text v0.3.0 // indirect rsc.io/quote v1.5.2 ) $ golang.org/x/text 包已经升级到最新版本了. go.mod 文件也更新了. indirect 注释表示这不是一个直接被该模块使用的依赖, 而是间接依赖.\n让我们试图更新 rsc.io/sampler 的次要版本, 使用相同的方式, 并运行测试:\n$ go get rsc.io/sampler go: finding rsc.io/sampler v1.99.99 go: downloading rsc.io/sampler v1.99.99 go: extracting rsc.io/sampler v1.99.99 $ go test --- FAIL: TestHello (0.00s) hello_test.go:8: Hello() = \u0026quot;99 bottles of beer on the wall, 99 bottles of beer, ...\u0026quot;, want \u0026quot;Hello, world.\u0026quot; FAIL exit status 1 FAIL example.com/hello 0.014s $ 测试失败了, 这个版本不兼容我们的用例. 看一看这个模块有哪些版本:\n$ go list -m -versions rsc.io/sampler rsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99 $ 换个版本试试, 我们已经使用 1.3.0 了, 或许 1.3.1 可以兼容.\n$ go get rsc.io/[email protected] go: finding rsc.io/sampler v1.3.1 go: downloading rsc.io/sampler v1.3.1 go: extracting rsc.io/sampler v1.3.1 $ go test PASS ok example.com/hello 0.022s $ 通过在 go get 中指明版本号, 默认是 @latest.\n添加对新的主版本的依赖 添加一个新的功能, 修改 hello.go 文件:\npackage hello import ( \u0026quot;rsc.io/quote\u0026quot; quoteV3 \u0026quot;rsc.io/quote/v3\u0026quot; ) func Hello() string { return quote.Hello() } func Proverb() string { return quoteV3.Concurrency() } 然后添加一个新的测试, 在 hello_test.go 中:\nfunc TestProverb(t *testing.T) { want := \u0026quot;Concurrency is not parallelism.\u0026quot; if got := Proverb(); got != want { t.Errorf(\u0026quot;Proverb() = %q, want %q\u0026quot;, got, want) } } 然后, 重新测试:\n$ go test go: finding rsc.io/quote/v3 v3.1.0 go: downloading rsc.io/quote/v3 v3.1.0 go: extracting rsc.io/quote/v3 v3.1.0 PASS ok example.com/hello 0.024s $ 注意到, 我们的模块现在依赖 rsc.io/quote 和 rsc.io/quote/v3.\n$ go list -m rsc.io/q... rsc.io/quote v1.5.2 rsc.io/quote/v3 v3.1.0 $ 每一个 GO 模块的主要版本, 都会使用一个不同的路径: 从 v2 开始, 路径必须以主版本号结尾. 比如 rsc.io/quote 的 v3 版本, 不再是 rsc.io/quote, 而是独立的名字 rsc.io/quote/v3. 这种用法叫做 语义导入版本化, 它给了不兼容的包(不同主版本的包)一个不同的名字.\ngo 命令允许构建包含任何特定模块路径的至多一个版本, 意味着每一个主版本最多一个路径: 一个 rsc.io/quote, 一个 rsc.io/quote/v2, 一个 rsc.io/quote/v3等等. 这给予了模块作者一个清晰的规则, 一个模块路径是否可能出现副本: 一个程序不可能同时使用 rsc.io/quote v1.5.2 和 rsc.io/quote v1.6.0. 同时, 一个模块的不同主版本允许共存, 能帮助模块的消费者可以渐进地升级到新的主版本上.\n在这个例子中, 我们想要使用 rsc/quote/v3 v3.1.0 中的 quote.Concurrency, 但我们还没 做好准备迁移对 rsc.io/quote v1.5.2 的使用.\n在大型程序或代码库中, 逐步迁移的能力尤其重要.\n升级依赖到新的主版本 让我们继续完成迁移, 开始只使用 rsc.io/quote/v3. 因为主版本的改变, 我们预计到某些 APIs 可能已经被移除, 重命名, 或者以其他不兼容的方式改变了.\n通过阅读文档, 我们发现 Hello 已经变成了 HelloV3:\n$ go doc rsc.io/quote/v3 package quote // import \u0026quot;rsc.io/quote/v3\u0026quot; Package quote collects pithy sayings. func Concurrency() string func GlassV3() string func GoV3() string func HelloV3() string func OptV3() string $ 我们通过升级将 hello.go 中的 quote.Hello() 改变为 quoteV3.HelloV3().\npackage hello import quoteV3 \u0026quot;rsc.io/quote/v3\u0026quot; func Hello() string { return quoteV3.HelloV3() } func Proverb() string { return quoteV3.Concurrency() } 在这个节点上, 我们无需重命名导入了, 所以可以这样做:\npackage hello import \u0026quot;rsc.io/quote/v3\u0026quot; func Hello() string { return quote.HelloV3() } func Proverb() string { return quote.Concurrency() } 重新运行测试, 一切正常.\n移除未使用的依赖 我们已经移除了对 rsc.io/quote 的使用, 但它依然出现在 go list -m all 和 go.mod 文件中.\n为什么? 因为 go build 和 go test 可以轻易告诉我们某些东西丢失了, 需要添加, 但无法告诉我们某些东西可以安全地被移除.\n移除一个依赖, 只有在检查过模块中的所有包, 和这些包所有可能的构建标签组合 之后才能确定. 一个普通的构建命令是不会加载这些信息的, 所以它不能安全地移除依赖.\ngo mod tidy 可以清除这些未使用的包.\n结论 在 Go 中 Go 模块是依赖管理的未来. 模块功能在所有支持的 Go 版本中是可用的了(GO 1.11 之后).\n总结一下使用 Go 模块的工作流:\n go mod init 创建一个新的模块, 初始化 go.mod 文件 go build, go test 和其他的包构建命令会添加必要的依赖到 go.mod 文件中 go list -m all 打印出当前模块的依赖 go get 改变一个依赖的必要版本 go mod tidy 移除未使用的依赖 迁移到 Go 模块 Go 项目有各式各样的依赖管理策略. Vendoring 工具, 比如 dep 或 glide 是 非常流行的, 但它们在行为上非常不同, 且不能很好地兼容. 一些项目在 Git 仓库中存储它们的整个 GOPATH 目录. 其他一些可能仅依赖 go get 并期望在 GOPATH 中安装最新的依赖.\nGo 的模块系统, 在 Go 1.11 中引入, 提供了一个基于 go 命令的官方依赖解决方案.\n下面主要讲述了这个工具, 以及将一个项目转变为模块的技术.\n注意: 如果你的项目已经在版本 2.0.0 或以上, 当你添加一个 go.mod 文件时, 需要升级你的模块路径.\n迁移你的项目到 Go 模块 一个即将转换到 Go 模块的项目可能处于下面三种情况下:\n 一个全新的 Go 项目 一个使用依赖管理器的 Go 项目 一个没有任何依赖管理器的 Go 项目 这里将讨论后面两种情况.\n已有依赖管理器 转换一个已经有依赖管理器的项目, 使用下面的命令:\n$ git clone https://github.com/my/project [...] $ cd project $ cat Godeps/Godeps.json { \u0026quot;ImportPath\u0026quot;: \u0026quot;github.com/my/project\u0026quot;, \u0026quot;GoVersion\u0026quot;: \u0026quot;go1.12\u0026quot;, \u0026quot;GodepVersion\u0026quot;: \u0026quot;v80\u0026quot;, \u0026quot;Deps\u0026quot;: [ { \u0026quot;ImportPath\u0026quot;: \u0026quot;rsc.io/binaryregexp\u0026quot;, \u0026quot;Comment\u0026quot;: \u0026quot;v0.2.0-1-g545cabd\u0026quot;, \u0026quot;Rev\u0026quot;: \u0026quot;545cabda89ca36b48b8e681a30d9d769a30b3074\u0026quot; }, { \u0026quot;ImportPath\u0026quot;: \u0026quot;rsc.io/binaryregexp/syntax\u0026quot;, \u0026quot;Comment\u0026quot;: \u0026quot;v0.2.0-1-g545cabd\u0026quot;, \u0026quot;Rev\u0026quot;: \u0026quot;545cabda89ca36b48b8e681a30d9d769a30b3074\u0026quot; } ] } $ go mod init github.com/my/project go: creating new go.mod: module github.com/my/project go: copying requirements from Godeps/Godeps.json $ cat go.mod module github.com/my/project go 1.12 require rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca $ go mod init 会创建一个新的 go.mod 文件, 并自动导入依赖, 从 Godeps.json, Gopkg.lock 或其他支持的格式中.\ngo mod init 的参数是模块路径, 模块从哪里发现的位置.\n在继续之前, 先运行 go build 或 go test. 后续的步骤可能会修改你的 go.mod 文件, 如果你想要迭代更新, 这是最接近你的模块依赖规范的 go.mod 文件.\n$ go mod tidy go: downloading rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca go: extracting rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca $ cat go.sum rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca h1:FKXXXJ6G2bFoVe7hX3kEX6Izxw5ZKRH57DFBJmHCbkU= rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= $ go mod tidy 查找在你的模块中, 确实被导入的包. 它会为不在任何已知模块中的包添加模块依赖, 并删除任何没有提供导入包的模块. 如果一个模块提供的包只被还没有迁移到模块系统的项目使用, 这个模块依赖会使用 // indirect 注释标记.\n当提交对 go.mod 的改动到版本控制中, 一个好习惯是先运行 go mod tidy.\n最后, 检查代码构建成功, 并通过测试.\n$ go build ./... $ go test ./... [...] $ 注意, 其他的依赖管理器可能在个人包或者整个仓库的级别上指定依赖关系, 并且通常无法识别 go.mod 中指定的依赖. 因此, 你可能无法获得 和以前一样的每个包的完全相同的版本, 并且升级可能发生破坏性的变化. 因此, 通过审计生成的依赖来执行上面的命令非常重要.\n运行下面的命令:\n$ go list -m all go: finding rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca github.com/my/project rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca $ 并通过对比你的旧依赖管理文件来确定选择的版本是恰当的. 如果你发现一个版本不是你想要的, 可以通过 go mod why -m 或 go mod graph 命令来发现为什么, 并使用 go get 升级或降级到正确的版本.\n如果需要降级, go get 也可能会降级其他依赖来满足最小兼容性.\n$ go mod why -m rsc.io/binaryregexp [...] $ go mod graph | grep rsc.io/binaryregexp [...] $ go get rsc.io/[email protected] $ 没有使用包管理器 对于没有使用包管理器的 Go 项目, 从创建 go.mod 文件开始:\n$ git clone https://go.googlesource.com/blog [...] $ cd blog $ go mod init golang.org/x/blog go: creating new go.mod: module golang.org/x/blog $ cat go.mod module golang.org/x/blog go 1.12 $ 没有先前的依赖管理文件可以参考, 所以 go mod init 会创建 一个只有 module 和 go 指令的 go.mod 文件. 在这个例子中, 我们设置模块路径为 golang.org/x/blog, 是因为 那是它的 自定义导入路径. 用户可能使用这个路径导入包, 所以我们必须小心对待, 不要改变它.\nmodule 指令声明了模块路径, go 指令声明了用来兼容 这个模块的 Go 语言版本.\n现在, 运行 go mod tidy 添加依赖:\n$ go mod tidy go: finding golang.org/x/website latest go: finding gopkg.in/tomb.v2 latest go: finding golang.org/x/net latest go: finding golang.org/x/tools latest go: downloading github.com/gorilla/context v1.1.1 go: downloading golang.org/x/tools v0.0.0-20190813214729-9dba7caff850 go: downloading golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 go: extracting github.com/gorilla/context v1.1.1 go: extracting golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 go: downloading gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 go: extracting gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 go: extracting golang.org/x/tools v0.0.0-20190813214729-9dba7caff850 go: downloading golang.org/x/website v0.0.0-20190809153340-86a7442ada7c go: extracting golang.org/x/website v0.0.0-20190809153340-86a7442ada7c $ cat go.mod module golang.org/x/blog go 1.12 require ( github.com/gorilla/context v1.1.1 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 golang.org/x/text v0.3.2 golang.org/x/tools v0.0.0-20190813214729-9dba7caff850 golang.org/x/website v0.0.0-20190809153340-86a7442ada7c gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 ) $ cat go.sum cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= git.apache.org/thrift.git v0.0.0-20181218151757-9b75e4fe745a/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= [...] $ go mod tidy 为所有在你的模块中直接导入的包添加模块依赖, 并且创建一个 go.sum 文件 保存每一个库在特定版本下的校验和. 检查是否通过构建和测试:\n$ go build ./... $ go test ./... ok golang.org/x/blog 0.335s ? golang.org/x/blog/content/appengine [no test files] ok golang.org/x/blog/content/cover 0.040s ? golang.org/x/blog/content/h2push/server [no test files] ? golang.org/x/blog/content/survey2016 [no test files] ? golang.org/x/blog/content/survey2017 [no test files] ? golang.org/x/blog/support/racy [no test files] $ 注意, 当 go mod tidy 添加一个依赖时, 它会添加这个模块的最新版本. 如果你的 GOPATH 中包含了一个旧版本的依赖, 那么新版本可能会带来 破坏性的变化, 你可能在 go mod tidy, go build 或 go test 中 看到错误.\n如果发生了这种事, 试着用 go get 降级版本, 或者将你的模块兼容 每个依赖的最新版本.\n在模块下进行测试 一些测试可能在迁移到 Go 模块后需要调整.\n如果一个测试需要在包目录下写入文件, 当这个包目录位于模块缓存中时可能失败, 因为它是只读的. 在特定情况下, 这可能导致 go test all 失败. 测试应该复制它需要的文件并写到到一个临时目录中.\n如果一个测试依赖相对路径(../package-in-another-module) 来定位并 读取在另一个包中的文件, 如果包在另一个模块中, 测试会失败. 这个路径会被定位在模块缓存的版本化子目录中, 或者一个被 replace 指令指定的路径中.\n在这种情况下, 你可能需要复制测试输入到你的模块中, 或者将测试输入 从原始文件转为嵌入到 .go 源文件的数据.\n如果测试需要 go 命令在 GOPATH 模式下运行测试, 它可能失败. 在这种情况下, 你可能需要添加一个 go.mod 文件到要被测试的源代码树中, 或者明确地设置 GO111MODULE=off.\n发布一个版本 最终, 你应该为你的模块标记一个版本并发布. 如果你还没有发布任何版本, 这是可选的, 但是没有正式发布的版本, 下游的使用者在特定的提交上使用 伪版本, 这可能更难以支持.\n$ git tag v1.2.0 $ git push origin v1.2.0 你的新 go.mod 文件为你的模块定义了一个规范的导入路径, 并添加了最低的依赖版本. 如果你的用户已经使用正确的导入路径, 你的依赖也没有破坏性的改动, 那么添加 go.mod 文件是向后兼容的. 但它是一个重大的改变, 可能暴露已存在的问题.\n如果你已经现存的版本了, 你应该增加主版本号.\n导入和规范模块路径 每一个模块在 go.mod 中声明了它的模块路径. 引用模块中的包的每个 import 语句必须将模块路径作为包路径的前缀. 但是, go 命令可能遇到一个包含许多不同 远程导入路径 的仓库. 举个例子, golang.org/x/lint 和 github.com/golang/lint 都可以 解析为托管在 go.googlesource.com/lint 中的代码仓库. go.mod 文件包含的仓库声明为 golang.org/x/lint, 所以只有这条路径 对应了一个有效的模块.\nGo 1.4 提供了一个机制, 用于声明规范的导入路径, 通过 // import 注释, 但包作者并不总是提供它们. 因此, 在模块出现之前编写的代码可能使用了不规范的模块导入路径, 却没有因为路径不匹配而出现错误. 当使用模块后, 导入路径必须匹配规范的模块路径, 所以你需要更新 import 语句. 举个例子, 你可能需要将 import \u0026quot;github.com/golang/lint\u0026quot; 改为 import \u0026quot;golang.org/x/lint\u0026quot;.\n另一个问题是, 对于主版本大于等于 2 的模块, 模块的规范路径和它的存储库路径不一致. 一个主版本大于 1 的 Go 模块, 必须在它的模块路径之后加上主版本号作为后缀. 比如版本 2.0.0 必须添加后缀 /v2. 但是 import 语句可能引用了 模块中的包, 但没有提供版本后缀. 举个例子, 非模块用户在可能在版本 v2.0.1 中使用了 github.com/russross/blackfriday 而不是 github.com/russross/blackfriday/v2, 需要更新导入路径包含后缀 /v2.\n总结 对大多数用户而言, 转换为 Go 模块应该是一个简单的过程. 因为非规范的导入路径或者破坏性的依赖可能偶然导致出错.\n"
},
{
"uri": "/3-linux/mobaxterm/",
"title": "Mobaxterm",
"tags": [],
"description": "",
"content": " 简介 mobaxterm 是一个 windows 上的 shell 工具.\n对我而言, 最方便的是, 它的左右两栏分别是 stfp 和 shell.\n设置 local shell mobaxterm 有一个 local shell, 可以选择多种类型的终端, 包括 bash 和 WSL.\n我的配置如下:\n 勾选 Use Windows PATH environment 勾选 Paste using right click 设置 Persistent home directory 为用户的主目录 C:\\Users\\tzh "
},
{
"uri": "/2-docker/kubeadm/",
"title": "Kubeadm",
"tags": [],
"description": "",
"content": " 腾讯云上使用 kubeadm 我在腾讯云上有台 2h8g 的机器, 平时就用来吃灰, 偶尔实践一下 linux 上的各种操作. 这次就来尝试 kubeadm 在腾讯云上安装 kubernetes.\n因为腾讯云默认是 unbuntu 账户, 不是 root 账户, 所以基本所有命令都要加上 sudo.\n安装 kubeadm 软件 sudo apt-get update \u0026amp;\u0026amp; apt-get install -y apt-transport-https curl # 失败, 替换为下面的 alibaba 镜像 curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - # 使用 alibaba 的镜像 https://opsx.alibaba.com/mirror?lang=zh-CN curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add - # 其实就是 将 deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main # 这句话写到文件 /etc/apt/sources.list.d/kubernetes.list 中 sudo bash -c 'cat \u0026lt;\u0026lt;EOF \u0026gt;/etc/apt/sources.list.d/kubernetes.list deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main EOF' sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl # 标记为不被自动更新 sudo apt-mark hold kubelet kubeadm kubectl 初始化机器 # 临时关闭 swap sudo swapoff -a sudo sysctl net.bridge.bridge-nf-call-iptables=1 sudo sysctl net.bridge.bridge-nf-call-ip6tables=1 sudo systemctl stop firewalld sudo systemctl disable firewalld # 临时关闭 selinux sudo setenforce 0 设置集群 首先必须寻找 gcr.io 里的镜像的国内加速版本.\n# 获取配置文件 kubeadm config print init-defaults \u0026gt; init-config.yaml # 修改其中的 imageRepository 为 registry.cn-hangzhou.aliyuncs.com/google_containers # 修改其中的 localAPIEndpoint.advertiseAddress 为 0.0.0.0 # 预先下载镜像 kubeadm config images pull --config=init-config.yaml # 在开始初始化之前, 必须先选择 POD 网络插件, 有些插件需要你 init 的时候传递响应的参数 # 这里使用 Weave Net, 无需特殊设置 init 参数 sudo kubeadm init --config=init-config.yaml # 根据 init 里的提示操作 mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # 假如 worker node kubeadm join 10.0.1.12:6443 --token abcdef.0123456789abcdef \\ --discovery-token-ca-cert-hash sha256:9c0c030d9e415e5b1cd1402d4018f0f0c5ce264b44882a3ff31e6fe55052e8e8 # 让 master 也作为 worker node kubectl taint nodes --all node-role.kubernetes.io/master- # 安装网络插件 这里使用 Weave Net kubectl apply -f \u0026quot;https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\\n')\u0026quot; # 验证 node status 为 ready 就行了 kubectl get nodes kubectl get pods -n kube-system 加入集群 # 运行 master 集群上 kubeadm init 最后的提示 kubeadm join --token \u0026lt;token\u0026gt; \u0026lt;master-ip\u0026gt;:\u0026lt;master-port\u0026gt; --discovery-token-ca-cert-hash sha256:\u0026lt;hash\u0026gt; # token 会在 24 小时候过期, 所以需要重新生成 kubeadm token list kubeadm token create # 获取 --discovery-token-ca-cert-hash openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2\u0026gt;/dev/null | \\ openssl dgst -sha256 -hex | sed 's/^.* //' 重置集群 # 重置器群, 如果发生了任何意想不到的情况 sudo kubeadm reset sudo iptables -F \u0026amp;\u0026amp; sudo iptables -t nat -F \u0026amp;\u0026amp; sudo iptables -t mangle -F \u0026amp;\u0026amp; sudo iptables -X 参考 install-kubeadm\n"
},
{
"uri": "/1-hugo/install/",
"title": "安装启动",
"tags": [],
"description": "",
"content": " 快速安装 Hugo 虽然是基于 go 语言的项目, 但最懒的方式还是去 releases 页面 手动下载预编译的二进制包.\n解压出来也只有单个的二进制文件, 如果想全局使用, 就添加到环境变量中吧. 但对我的博客项目来说, 没什么必要, 直接新建一个 bin 目录丢进去就行. 直接放在根目录也是行的, 甚至更方便.\n必要假设 接下来运行的命令都基于以下假设\n 根目录下有个 bin 文件, 里面是名为 hugo 的二进制程序 运行路径是根目录 因为都是在根目录运行的, 所以运行 hugo 时, 需要指定选项 -s ./blog 新建站点 新建一个站点, 取名为 blog, 这个是网站的根目录名称.\n./bin/hugo new site blog 添加主题 这里我使用的是 HUGO LEARN THEME 主题.\ngit submodule add https://github.com/matcornic/hugo-theme-learn.git blog/themes/hugo-theme-learn echo 'theme = \u0026quot;hugo-theme-learn\u0026quot;' \u0026gt;\u0026gt; blog/config.toml 使用 git 的 submodule 添加, 目的地路径是 blog/themes/hugo-theme-learn.\n同时, 修改配置文件 config.toml 中的选项, 将 theme 的值改为对应的主题, 必须跟 blog/themes/ 目录下 的主题名称一致.\n添加文章 添加文章的基础命令是 hugo new, 但因为涉及到主题相关的内容, 在后面介绍.\n./bin/hugo new posts/doc.md -s ./blog 启动 ./bin/hugo server -D -s ./blog 生成静态网站 先去配置文件中修改一下导出目录, 这里我设置为根目录下 public 目录, 添加如下配置\npublishdir = \u0026quot;../public\u0026quot; 运行命令\n./bin/hugo -s ./blog "
},
{
"uri": "/1-hugo/",
"title": "Hugo",
"tags": [],
"description": "",
"content": " Hugo 使用指南 简介 这个站点是使用 Hugo 构建的, 记录使用 Hugo 的过程.\n"
},
{
"uri": "/4-go/template/",
"title": "Template 笔记",
"tags": [],
"description": "",
"content": " 简介 包名 text/template , 文档\n实现了数据驱动的模版, 用于生成文本输出.\n要在生成 html 文件, 可以使用包 html/template .\n语法 模版中的注解引用数据结构的元素来控制执行, 获取值并显示. 数据结构通常是一个 struct 中的 field 或者一个 map 中的 key. 执行模版时遍历结构并设置光标 使用一个前缀 . 表示, 叫做 dot, 作为执行过程中当前位置的值.\n数据执行或者控制语句都是有 {{ 和 }} 包围的.\ntype Inventory struct { Material string Count uint } sweaters := Inventory{\u0026quot;wool\u0026quot;, 17} tmpl, err := template.New(\u0026quot;test\u0026quot;).Parse(\u0026quot;{{.Count}} items are made of {{.Material}}\u0026quot;) if err != nil { panic(err) } err = tmpl.Execute(os.Stdout, sweaters) if err != nil { panic(err) } 使用 {{- 会删除在它之前的所有尾随空格, -}} 会删除它之后的所有尾随空格.\n\u0026quot;{{23 -}} \u0026lt; {{- 45}}\u0026quot; \u0026quot;23\u0026lt;45\u0026quot; Actions 所有 Actions 的用法说明.\n{{/* 评论 */}} {{- /* a comment with white space trimmed from preceding and following text */ -}} 评论会被丢弃, 可能包含换行符. 评论不能嵌套, 必须以分隔符开始并结束. {{pipeline}} 默认的文本表示 (和 fmt.Print 打印的结果一样) , pipeline 的值会被复制到输出中. {{if pipeline}} T1 {{end}} 如果 pipeline 是空值, 不会产生任何东西; 否则, T1 会执行. 空值是 false, 0, 任何 nil 指针或 interface 值, 任何长度为 0 的 array, slice, map, 或 string. dot 没影响. {{if pipeline}} T1 {{else}} T0 {{end}} 如果 pipeline 是空值, T0 会执行; 否则, T1 会执行. dot 没影响. {{if pipeline}} T1 {{else if pipeline}} T0 {{end}} 简化 if-else 链, 一个 if 的 else 可能直接包含另一个 if. 和下面的语句效果一样 {{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}} {{range pipeline}} T1 {{end}} pipeline 的值必须是一个 array, slice, map, 或 channel. 如果 pipeline 的值长度为 0, 什么都不会输出; 否者, dot 会设置为 array, slice, or map 中的每一个元素, 然后执行 T1. 如果值是一个 map 且 它的 keys 是基础 type 且有一个定义好的顺序 (\u0026quot;可比较的\u0026quot;), 元素们会按定义好的顺序访问. {{range pipeline}} T1 {{else}} T0 {{end}} pipeline 的值必须是一个 array, slice, map, or channel. 如果 pipeline 的值长度为 0, dot 没影响, 并且 T0 会被执行; 否者, dot 会被设置为 array, slice, or map 中的每一个元素, 且 T1 会被执行. {{template \u0026quot;name\u0026quot;}} 名字为 name 的模版会被执行, 使用 nil 作为模版的参数. {{template \u0026quot;name\u0026quot; pipeline}} 名字为 name 的模版会被执行, 并将 dot 设置为 pipeline 的值, 作为模版参数. {{block \u0026quot;name\u0026quot; pipeline}} T1 {{end}} 一个 block 是一个快捷方式, 定义一个如下的模版 {{define \u0026quot;name\u0026quot;}} T1 {{end}} 并且就地执行 {{template \u0026quot;name\u0026quot; pipeline}} 典型用法是定义一组根模版, 然后通过重写 block 模版来自定义. {{with pipeline}} T1 {{end}} 如果 pipeline 是空的, 什么都不会产生; 否者, dot 被设置为 pipeline 的值, 且 T1 会被执行. {{with pipeline}} T1 {{else}} T0 {{end}} 如果 pipeline 是空的, dot 不受影响且 T0 会被执行; 否则 dot 被执行为 pipeline 的值, 且 T1 会被执行. Arguments 一个 argument 是一个简单的值, 由以下之一表示:\n- 一个 boolean, string, character, integer, floating-point, imaginary or complex constant in Go syntax. 这些就像是 Go 的无类型常量. 注意, 和 Go 一样, 当分配或传递给一个函数时, 是否发生大整数溢出取决于 主机的 int 是 32 还是 64 位的. - 关键字 nil, 表示一个无类型的 Go nil. - 字符 '.' (点号): . 结果是 dot 的值. - 一个变量名, 它可能是一个 (可能为空的) 字母数字 string, 有一个美元前缀, 比如 $piOver2 or $ 结果是变量的值. 变量在下面描述. - data 中的一个 field 的名字, data 必须是一个 struct, 有一个点前缀, 比如 .Field 结果是 field 的值. Field 是可以链式调用的: .Field1.Field2 Fields 也可以在变量上调用, 包括链式调用: $x.Field1.Field2 - data 中的一个 key 的名字, data 必须是一个 map, 有一个点前缀, 比如 .Key 结果是 map 中 key 对应的值. Key 调用可以是链式的, 也可以无限组合 field: .Field1.Key1.Field2.Key2 虽然 key 必须是一个字母数字标识符, 但不需要像 field 一样 必须以大写字母开头. Keys 也可以在变量上执行, 包括链式调用: $x.key1.key2 - data 的一个 niladic 方法的名字, 有一个点前缀, 比如: .Method 结果是使用 dot 作为接收者调用方法后的值, dot.Method(). 这个方法必须有一个访问值 (任何类型)或 者两个返回值, 第二个返回值是 error. 如果有两个返回值, 返回的 error 是 non-nil, 执行会中断, 一个 error 会作为执行的结果返回给调用者. 方法调用可以链式或者组合 fields 和 keys, 在任何层级上: .Field1.Key1.Method1.Field2.Key2.Method2 方法也能在变量上执行, 包括链式调用: $x.Method1.Field - 一个 niladic 函数的名字, 比如 fun 结果是调用这个函数的返回值, fun(). 返回类型和返回值和 methods 一样. Functions 和 function 名称在下面讲述. - 一个带有括号的上述实例, 用于分组. 结果可以像 field 或者 map key 一样调用. print (.F1 arg1) (.F2 arg2) (.StructValuedMethod \u0026quot;arg\u0026quot;).Field Pipelines 一个 pipeline 是一组链式的命令. 一个命令是一个简单值(argument) 或者一个可能使用多个参数的函数或方法调用.\nArgument 结果是执行 argument 后的值. .Method [Argument...] method 可以是单独的或者 chain 的最后一个, 但是不像在chain 中的 method, 这两种方式可以使用参数. 结果是使用参数调用方法后的值: dot.Method(Argument1, etc.) functionName [Argument...] 结果是使用 name 调用函数后的值: function(Argument1, etc.) Functions 和 function 名称在下面讲述. 一个 pipeline 可能被管道字符 | 分隔的一系列命令链接起来. 在一个链接的 pipeline 中, 每一个命令的结果会作为最后一个参数传递到后续的命令中. pipeline 中的最后一个命令的输出作为 pipeline 的值.\n命令的输出可能是一个值或者两个值, 第二个值是 error 类型. 如果第二个参数存在, 且是 non-nil, 执行会终止, 且 error 会返回给调用者.\nVariables 一个 action 中的 pipeline 可能初始化一个变量来保存结果. 初始化有这样的语法:\n$variable := pipeline $variable 是变量的名字. 一个声明了变量的 action 没有输出.\n先前声明的变量也重新分配, 使用语句:\n$variable = pipeline 如果一个 range action 初始化一个变量, 变量设置为每一次迭代中的连续元素. range 可能声明两个元素, 通过逗号分隔:\nrange $index, $element := pipeline $index and $element 会被设置为 array/slice 的索引和值, map 的 key 和 value. 注意, 如果只有一个变量, 它会被设置为元素(而不是索引或 key), 这和 Go range 语句的用法相反.\n一个变量的范围直到声明它的控制结构 (\u0026ldquo;if\u0026rdquo;, \u0026ldquo;with\u0026rdquo;, or \u0026ldquo;range\u0026rdquo;) 中的 end action 为止. 如果没有控制结构, 则直到模版的结尾为止. 一个模版调用不会从它的调用点继承变量.\n当执行开始时, $ 被设置为 data argument 传递给 Execute, 也就是 dot 的起始值.\nExamples 这里有一些单行的模版演示了 pipeline 和变量. 所有都会产生 \u0026quot;output\u0026quot;\n{{\u0026quot;\\\u0026quot;output\\\u0026quot;\u0026quot;}} 一个 string 常量. {{`\u0026quot;output\u0026quot;`}} 一个 raw string 常量. {{printf \u0026quot;%q\u0026quot; \u0026quot;output\u0026quot;}} 一个函数调用. {{\u0026quot;output\u0026quot; | printf \u0026quot;%q\u0026quot;}} 一个函数调用, 它的最后一个参数来自于前一个命令. {{printf \u0026quot;%q\u0026quot; (print \u0026quot;out\u0026quot; \u0026quot;put\u0026quot;)}} 一个括号内的参数. {{\u0026quot;put\u0026quot; | printf \u0026quot;%s%s\u0026quot; \u0026quot;out\u0026quot; | printf \u0026quot;%q\u0026quot;}} 一个更复杂的调用. {{\u0026quot;output\u0026quot; | printf \u0026quot;%s\u0026quot; | printf \u0026quot;%q\u0026quot;}} 一个更长的 chain. {{with \u0026quot;output\u0026quot;}}{{printf \u0026quot;%q\u0026quot; .}}{{end}} 一个使用 dot 的 action. {{with $x := \u0026quot;output\u0026quot; | printf \u0026quot;%q\u0026quot;}}{{$x}}{{end}} 一个创建并使用变量的 action. {{with $x := \u0026quot;output\u0026quot;}}{{printf \u0026quot;%q\u0026quot; $x}}{{end}} 一个使用另一个 action 中的变量的 action. {{with $x := \u0026quot;output\u0026quot;}}{{$x | printf \u0026quot;%q\u0026quot;}}{{end}} 和上面相同, 但是使用 pipeline. Functions 在执行期间, 函数可以在两个 function maps 中找到: 第一个在模版中, 第二个在全局的 function map 中. 默认的, 没有函数被定义在模版中, 但是可以使用 Funcs method 添加它们.\n预定义的全局函数的命名如下:\nand 对它的参数们进行布尔 AND 运算, 返回第一个空参数或者最后一个参数, \u0026quot;and x y\u0026quot; 表现得像是 \u0026quot;if x then y else x\u0026quot;. 所有的参数都会被执行. call 返回第一个参数调用后的结果, 第一个参数必须是函数, 其他的参数作为它的参数. 因此 \u0026quot;call .X.Y 1 2\u0026quot; 在 Go 调用中就是 dot.X.Y(1, 2), Y 是一个 func-valued field, map entry, 或者类似的. 第一个参数必须是 function 类型的值(不同于预定义的函数, 比如 print). 这个函数必须返回一个或两个值, 第二个值的类型是 error. 如果参数不匹配函数的要求, 或者返回的值 error 是 non-nil, 执行会终止. html 返回它的参数的转义过的 HTML 文本表示. 除了一些例外, 这个函数在 html/template 是不可用的. index 返回第一个参数的索引, 通过其他参数作为索引. 因此 \u0026quot;index x 1 2 3\u0026quot; 在 Go 语法中就是 x[1][2][3]. 每一个索引值必须是 map, slice, 或 array. js 返回它的参数的转义过的 JavaScript 文本表示. len 返回它的参数的长度. not 返回它的单个参数的布尔相反类型. or 对它的参数们进行布尔 NOT 运算, 返回第一个非空参数或者最后一个. \u0026quot;or x y\u0026quot; 表现得像是 \u0026quot;if x then x else y\u0026quot;. 所有的参数都会被执行. print fmt.Sprint 的别名. printf fmt.Sprintf 的别名. println fmt.Sprintln 的别名. urlquery 返回参数们的 URL query 形式, 并对每个参数进行转义后的文本表示. 除了一些例外, 这个函数在 html/template 是不可用的. 布尔函数将任何零值设置为 false, 非零值设置为 true.\n还有一组二元的比较运算符被定义为函数:\neq Returns the boolean truth of arg1 == arg2 ne Returns the boolean truth of arg1 != arg2 lt Returns the boolean truth of arg1 \u0026lt; arg2 le Returns the boolean truth of arg1 \u0026lt;= arg2 gt Returns the boolean truth of arg1 \u0026gt; arg2 ge Returns the boolean truth of arg1 \u0026gt;= arg2 为了简化多元相等比较, eq 支持两个或多个参数, 会将第一个参数和其他参数比较, 等价于:\narg1==arg2 || arg1==arg3 || arg1==arg4 ... (不像 Go 中的 || , eq 是一个函数调用, 所有的参数都会被执行).\n比较函数只适用于基本类型(或者基本类型的别名 比如 \u0026ldquo;type Celsius float32\u0026rdquo;). 它们实现了 Go 比较值的规则, 除了忽略大小和确切的类型, 所以任何整型, 有符号还是无符号的, 都可以和其他整型比较. (算术值比较, 而不是 bit 匹配, 所以所有的负整数小于所有的无符号整数). 但是, 和以前一样, 整型不能和 float32 之类的浮点数比较.\nAssociated templates 每个模版都在创建时通过一个字符串命名. 每个模版都可以关联零个或多个其他的模版,, 并通过名字调用 所以关联是可以传递的, 并形成了一个模版的命名空间.\n一个模版可能使用一个模版调用来实例化另一个关联的模版, 参见上面 template action 的说明. name 必须是与包含调用的模版有关联的模版.\nNested template definitions 当解析一个模版时, 可能定义另一个模版, 并与正在解析的模版相关联. 模版定义必须出现在模版的最顶层, 就像是 Go 程序中的全局变量.\n这种定义的语法是使用 define action 和 end action 包围每一个模版的定义.\ndefine action 通过提供一个字符串常量来命名模版. 这里有个简单的例子:\n`{{define \u0026quot;T1\u0026quot;}}ONE{{end}} {{define \u0026quot;T2\u0026quot;}}TWO{{end}} {{define \u0026quot;T3\u0026quot;}}{{template \u0026quot;T1\u0026quot;}} {{template \u0026quot;T2\u0026quot;}}{{end}} {{template \u0026quot;T3\u0026quot;}}` 这定义了两个模版, T1 和 T2. 第三个模版 T3 在执行的时候调用了它们. 最后, 调用了 T3. 执行这个模版会生成文本:\nONE TWO 通过构造, 一个模版可能驻留在一个关联中. 如果有必要让模版在多个关联中可被寻址, 模版定义必须被解析多次来创建一个不同的 *Template 值, 或者必须听过 Clone 或 AddParseTree 方法复制.\n解析可能被调用多次来组合不同关联的模版. 查看 ParseFiles 和 ParseGlob 函数和方法, 它们提供了解析存储在文件中的模版的简单方式.\n一个模版可能直接执行或者通过 ExecuteTemplate, 后者执行 name 标识的模版. 为了调用我们在上面的例子, 我们可以写成:\nerr := tmpl.Execute(os.Stdout, \u0026quot;no data needed\u0026quot;) if err != nil { log.Fatalf(\u0026quot;execution failed: %s\u0026quot;, err) } 或者基于特定的名字来执行模版:\nerr := tmpl.ExecuteTemplate(os.Stdout, \u0026quot;T2\u0026quot;, \u0026quot;no data needed\u0026quot;) if err != nil { log.Fatalf(\u0026quot;execution failed: %s\u0026quot;, err) } "
},
{
"uri": "/3-linux/gitlab/",
"title": "Gitlab",
"tags": [],
"description": "",
"content": " 简介 gitlab 不仅是一个 git 代码托管平台, 更是 DevOps 平台. 这点是我最喜欢的, 所以迫切想要实践一下.\n安装 对我而言, 主要有两种安装方式: docker 和 kubernetes.\n当然, 装完主体之后, 还需要安装 gitlab runner.\nTODO: 经过我一段时间的摸索之后, 还是有点问题\n以下的安装方式都是基于 docker-compose 的\n安装 gitlab 主体 首先, 安装之前, 我们需要确定以下几点.\n 确定 external_url, 这个是用户可以访问的外部链接, 参考 这个链接 是否使用内置的 nginx, 如果使用外部的 nginx, 可以参考 这个链接 是否使用 HTTPS, 参考 这个链接 这其中的几点对我来说非常致命, 因为我手头没有备案的域名, 所以我在腾讯云上部署就非常蛋疼.\n如果你只是想要一个 ip 地址显示的 gitlab, 那么还是挺容易的.\n# docker-compose.yml version: \u0026quot;3.7\u0026quot; services: gitlab: image: \u0026quot;gitlab/gitlab-ce:12.1.1-ce.0\u0026quot; restart: \u0026quot;no\u0026quot; hostname: \u0026quot;gitlab\u0026quot; environment: GITLAB_OMNIBUS_CONFIG: | external_url 'http://x.x.x.x:3080' # nginx['enable'] = false gitlab_rails['gitlab_shell_ssh_port'] = 3022 web_server['external_users'] = ['nginx', 'www-data'] # Add any other gitlab.rb configuration here, each on its own line ports: - \u0026quot;3080:80\u0026quot; # - '3043:443' - \u0026quot;3022:22\u0026quot; volumes: # - \u0026quot;/srv/gitlab/data/gitlab-workhorse:/var/opt/gitlab/gitlab-workhorse\u0026quot; - \u0026quot;/srv/gitlab/config:/etc/gitlab\u0026quot; - \u0026quot;/srv/gitlab/logs:/var/log/gitlab\u0026quot; - \u0026quot;/srv/gitlab/data:/var/opt/gitlab\u0026quot; networks: - git_net networks: git_net: name: git_net 简单介绍一下, 上面这个 docker-compose.yml 只有一个叫做 gitlab 的服务, 要修改的是里面的 external_url, 将它修改为自己的 ip 地址加映射的主机端口就行了, 上面文件中映射到了 3080 端口.\n同时, 也将数据都保存在了本机的 /srv/gitlab 目录中. 分别是配置文件, 日志和内部数据. 主配置文件叫做 gitlab.rb, 官方的配置文档请看 这个链接.\n然后, 你就能访问了.\n启用 HTTPS 要使用 HTTPS, 文档中介绍了两种方式, 自动的和手动的.\n自动的方式使用了 Let\u0026rsquo;s Encrypt, 可以参考 这个链接.\n手动的方式是要复制证书到指定的位置.\n两种方式都要修改的一个选项是 external_url, 将域名设置为 https 开头的就行了.\n当然, 在国内, 如果你的域名没有备案过, letsencrypt 的验证会失败. 因为验证被拦截了.\n使用外部 nginx 如果要使用内部的服务器, 先启用配置中的 nginx['enable'] = false.\n然后是创建 nginx 的配置文件. 我对这一块不太熟, 反正我创建出来的网站有点问题.\n有两个地方可以参考 1 和 2.\n在使用外部 nginx 之后, 容器暴露出的 80 就会没有用.\n## GitLab ## ## Modified from nginx http version ## Modified from http://blog.phusion.nl/2012/04/21/tutorial-setting-up-gitlab-on-debian-6/ ## Modified from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html ## ## Lines starting with two hashes (##) are comments with information. ## Lines starting with one hash (#) are configuration parameters that can be uncommented. ## ################################## ## CONTRIBUTING ## ################################## ## ## If you change this file in a Merge Request, please also create ## a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests ## ################################### ## configuration ## ################################### ## ## See installation.md#using-https for additional HTTPS configuration details. upstream gitlab-workhorse { # GitLab socket file, # for Omnibus this would be: unix:/var/opt/gitlab/gitlab-workhorse/socket server unix:/srv/gitlab/data/gitlab-workhorse/socket fail_timeout=0; } map $http_upgrade $connection_upgrade_gitlab_ssl { default upgrade; '' close; } ## NGINX 'combined' log format with filtered query strings log_format gitlab_ssl_access $remote_addr - $remote_user [$time_local] \u0026quot;$request_method $gitlab_ssl_filtered_request_uri $server_protocol\u0026quot; $status $body_bytes_sent \u0026quot;$gitlab_ssl_filtered_http_referer\u0026quot; \u0026quot;$http_user_agent\u0026quot;; ## Remove private_token from the request URI # In: /foo?private_token=unfiltered\u0026amp;authenticity_token=unfiltered\u0026amp;feed_token=unfiltered\u0026amp;... # Out: /foo?private_token=[FILTERED]\u0026amp;authenticity_token=unfiltered\u0026amp;feed_token=unfiltered\u0026amp;... map $request_uri $gitlab_ssl_temp_request_uri_1 { default $request_uri; ~(?i)^(?\u0026lt;start\u0026gt;.*)(?\u0026lt;temp\u0026gt;[\\?\u0026amp;]private[\\-_]token)=[^\u0026amp;]*(?\u0026lt;rest\u0026gt;.*)$ \u0026quot;$start$temp=[FILTERED]$rest\u0026quot;; } ## Remove authenticity_token from the request URI # In: /foo?private_token=[FILTERED]\u0026amp;authenticity_token=unfiltered\u0026amp;feed_token=unfiltered\u0026amp;... # Out: /foo?private_token=[FILTERED]\u0026amp;authenticity_token=[FILTERED]\u0026amp;feed_token=unfiltered\u0026amp;... map $gitlab_ssl_temp_request_uri_1 $gitlab_ssl_temp_request_uri_2 { default $gitlab_ssl_temp_request_uri_1; ~(?i)^(?\u0026lt;start\u0026gt;.*)(?\u0026lt;temp\u0026gt;[\\?\u0026amp;]authenticity[\\-_]token)=[^\u0026amp;]*(?\u0026lt;rest\u0026gt;.*)$ \u0026quot;$start$temp=[FILTERED]$rest\u0026quot;; } ## Remove feed_token from the request URI # In: /foo?private_token=[FILTERED]\u0026amp;authenticity_token=[FILTERED]\u0026amp;feed_token=unfiltered\u0026amp;... # Out: /foo?private_token=[FILTERED]\u0026amp;authenticity_token=[FILTERED]\u0026amp;feed_token=[FILTERED]\u0026amp;... map $gitlab_ssl_temp_request_uri_2 $gitlab_ssl_filtered_request_uri { default $gitlab_ssl_temp_request_uri_2; ~(?i)^(?\u0026lt;start\u0026gt;.*)(?\u0026lt;temp\u0026gt;[\\?\u0026amp;]feed[\\-_]token)=[^\u0026amp;]*(?\u0026lt;rest\u0026gt;.*)$ \u0026quot;$start$temp=[FILTERED]$rest\u0026quot;; } ## A version of the referer without the query string map $http_referer $gitlab_ssl_filtered_http_referer { default $http_referer; ~^(?\u0026lt;temp\u0026gt;.*)\\? $temp; } ## Redirects all HTTP traffic to the HTTPS host server { ## Either remove \u0026quot;default_server\u0026quot; from the listen line below, ## or delete the /etc/nginx/sites-enabled/default file. This will cause gitlab ## to be served if you visit any address that your server responds to, eg. ## the ip address of the server (http://x.x.x.x/) listen 0.0.0.0:80; listen [::]:80 ipv6only=on; server_name blog.iftodo.com; ## Replace this with something like gitlab.example.com server_tokens off; ## Don't show the nginx version number, a security best practice return 301 https://$http_host$request_uri; access_log /var/log/nginx/gitlab_access.log gitlab_ssl_access; error_log /var/log/nginx/gitlab_error.log; } ## HTTPS host server { listen 0.0.0.0:443 ssl; listen [::]:443 ipv6only=on ssl http2; server_name blog.iftodo.com; ## Replace this with something like gitlab.example.com server_tokens off; ## Don't show the nginx version number, a security best practice ## Strong SSL Security ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html \u0026amp; https://cipherli.st/ ssl on; ssl_certificate /etc/letsencrypt/live/blog.iftodo.com/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/blog.iftodo.com/privkey.pem; # managed by Certbot # include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot # GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs ssl_ciphers \u0026quot;ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4\u0026quot;; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 5m; ## See app/controllers/application_controller.rb for headers set ## [Optional] If your certficate has OCSP, enable OCSP stapling to reduce the overhead and latency of running SSL. ## Replace with your ssl_trusted_certificate. For more info see: ## - https://medium.com/devops-programming/4445f4862461 ## - https://www.ruby-forum.com/topic/4419319 ## - https://www.digitalocean.com/community/tutorials/how-to-configure-ocsp-stapling-on-apache-and-nginx # ssl_stapling on; # ssl_stapling_verify on; # ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt; # resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired # resolver_timeout 5s; ## [Optional] Generate a stronger DHE parameter: ## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096 ## # ssl_dhparam /etc/ssl/certs/dhparam.pem; ## [Optional] Enable HTTP Strict Transport Security # add_header Strict-Transport-Security \u0026quot;max-age=31536000; includeSubDomains\u0026quot;; ## Real IP Module Config ## http://nginx.org/en/docs/http/ngx_http_realip_module.html real_ip_header X-Real-IP; ## X-Real-IP or X-Forwarded-For or proxy_protocol real_ip_recursive off; ## If you enable 'on' ## If you have a trusted IP address, uncomment it and set it # set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24 ## Individual nginx logs for this GitLab vhost access_log /var/log/nginx/gitlab_access.log gitlab_ssl_access; error_log /var/log/nginx/gitlab_error.log; location / { client_max_body_size 0; gzip off; ## https://github.com/gitlabhq/gitlabhq/issues/694 ## Some requests take more than 30 seconds. proxy_read_timeout 300; proxy_connect_timeout 300; # proxy_redirect off; proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Ssl on; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade_gitlab_ssl; proxy_pass http://gitlab-workhorse; } error_page 404 /404.html; error_page 422 /422.html; error_page 500 /500.html; error_page 502 /502.html; error_page 503 /503.html; location ~ ^/(404|422|500|502|503)\\.html$ { # Location to the GitLab's public directory, # for Omnibus this would be: /opt/gitlab/embedded/service/gitlab-rails/public root /home/git/gitlab/public; internal; } } 我这里使用的是 2 中的文件, 主要修改的地方是:\n server_name 替换为自己的域名 access_log 的位置 error_log 的位置 upstream gitlab-workhorse 中 server 的位置, 容器内是在 /var/opt/gitlab/gitlab-workhorse 注释掉了 proxy_redirect 其实, 我觉得这个不太对, 需要修改, 做个精简版出来.\n自己的困惑 我这边遇到的问题呢, 有以下几点.\n因为是基于 docker 安装的, 所以要使用域名, 外部必须有 nginx. 因为内部的 nginx 在 external_url 被设置为 https 之后会自动启用 letsencrypt. 除非使用这个设置 letsencrypt['enable'] = false. 简单来说, 使用外部 nginx 了, 内部就不用再开了, 跳转两次也挺浪费的. 然后, 未备案的域名会被劫持, 所以必须在外部启用 HTTPS.\n一顿操作之后, 我们发现网站虽然建立起来了, 但总是有几个请求会失败. 常见的失败请求有\n https://blog.iftodo.com/root/aa/refs/master/logs_tree/?format=js https://blog.iftodo.com/root/aa/commits/master/signatures 常见的错误是 An error occurred while loading commit signatures.\n这让我非常痛苦, 如果直接使用域名访问, 却没有问题.\n安装 runner 对我来说, 安装 gitlab 的主要目的是想使用它内置的 CI\u0026amp;CD. 所以, 必须安装 runner.\n首先, 登录自己的 gitlab 网站, 第一次是修改管理员的密码, 管理员的名字是 root. 登录之后, 在 https://blog.iftodo.com/admin/runners 里找到加入 runner 的必要参数.\n分别是\n URL registration token runner 的官方文档是 https://docs.gitlab.com/runner/.\n在 runner 启动之前必须在先配置一番, 因为我这里使用的是 docker-compose, 所以安装文档看的是 docker 安装方式.\n要配置 runner 和 gitlab 的链接, 使用的命令是 sudo gitlab-runner register.\n你可以直接在 docker 容器内使用上面的命令相互式运行并生产文件, 然后复制到外部. 这里, 为了方便, 我选择使用非交互式的一步完成, 命令有点长, 不过写在文件中却还行.\n# docker-compose.yml version: \u0026quot;3.7\u0026quot; services: runner: image: \u0026quot;gitlab/gitlab-runner:latest\u0026quot; restart: \u0026quot;no\u0026quot; depends_on: - gitlab volumes: - \u0026quot;/var/run/docker.sock:/var/run/docker.sock\u0026quot; - \u0026quot;./gitlab/ruuner:/etc/gitlab-runner\u0026quot; # extra_hosts: # - 'gitlab:172.25.0.2' networks: - git_net # run before runner to get config, gitlab must have start runner-config: image: \u0026quot;gitlab/gitlab-runner:latest\u0026quot; restart: \u0026quot;no\u0026quot; command: | register --non-interactive --url=\u0026quot;http://gitlab\u0026quot; --registration-token=\u0026quot;TsBj3wds5uiteVYZUmXF\u0026quot; --executor=\u0026quot;docker\u0026quot; --docker-image=\u0026quot;alpine:latest\u0026quot; --docker-privileged=\u0026quot;true\u0026quot; --docker-pull-policy=\u0026quot;if-not-present\u0026quot; --docker-network-mode=\u0026quot;git_net\u0026quot; --description=\u0026quot;docker-runner\u0026quot; --tag-list=\u0026quot;docker\u0026quot; --run-untagged=\u0026quot;true\u0026quot; --locked=\u0026quot;false\u0026quot; --access-level=\u0026quot;not_protected\u0026quot; volumes: - \u0026quot;./gitlab/ruuner:/etc/gitlab-runner\u0026quot; networks: - git_net networks: git_net: name: git_net 这个文件和安装 gitlab 时的配置文件是同一个, 为了简单, 省略了 gitlab 的部分, 自行合起来吧.\n首先, 在自建的 gitlab 网站获取 URL 和 registration token 之后, 替换掉文件中 --url \u0026quot;http://gitlab\u0026quot; 和 --registration-token=\u0026quot;TsBj3wds5uiteVYZUmXF\u0026quot;. --url \u0026quot;http://gitlab\u0026quot; 其实不用改, 因为可以靠 docker-compose 的子网通讯.\n然后, 运行 docker-compose run runner-config, 这样配置文件就保留到主机了. 需要注意的一点是, 多次运行这个命令后, 配置文件会整合起来, 这样就会在配置文件中 生成多个 runner 了. 所以, 建议最好在运行之前删除 ./gitlab/ruuner 目录.\n生成配置文件之后, 就可以使用开启 runner 服务了, docker-compose up -d runner.\n配置文件 配置文件 .gitlab-ci.yml (0 ko) docker-compose.yml (1 ko) nginx_gitlab.conf (7 ko) "
},
{
"uri": "/2-docker/helm/",
"title": "Helm",
"tags": [],
"description": "",
"content": " Helm 简介 Helm 是 kubernetes 上的包管理器.\nHelm Charts 帮助你定义, 安装, 升级在 kubernetes 上的应用.\n安装 Helm 分为 client(helm) 和 server(Tiller).\n安装 client 去 github 上的 release 页面 上下载预编译的二进制文件.\n解压之后放在 bin 目录下就行, linux 通常可选 /usr/local/bin/helm. windows 就自己建个目录, 然后添加到环境变量 Path 中.\n安装 server # 直接安装会失败, 因为镜像下载不下来 helm init # 使用 ali 的镜像 helm init --tiller-image=registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.14.2 helm init --upgrade --tiller-image=registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.14.2 # 检查安装结果 kubectl get pods --namespace kube-system helm version 如果前面 helm init 失败的话, 使用下面的两种方式之一删除 helm init\nkubectl delete deployment tiller-deploy --namespace kube-system helm reset 使用 安装 Helm 主要是为了使用 draft, 所以暂时还没看文档.\n参考 install helm k8s-for-docker-desktop "
},
{
"uri": "/1-hugo/theme/",
"title": "Hugo New Theme 使用指南",
"tags": [],
"description": "",
"content": " 简介 这里, 我使用的是 Hugo New Theme 主题.\n这个主题是我在 search 分类下, 一眼就相中的, 可能是紫色太耀眼了. 其实, 我就是想把它作为自己的博客和笔记用, 有个搜索框方便些. 也可能是因为它的结构有点像 Python 很多库所用的文档生成器 Sphinx.\n官方的简介里也说了, 这个主题是完全为 文档 设计的.\n安装配置 前面有介绍过安装主题. 基本上有两种方式:\n 直接下载主题的 zip 包, 然后手动解压到 themes 目录下 使用 git submodule 添加依赖 简单介绍下 git submodule 的用法.\ngit submodule add https://github.com/matcornic/hugo-theme-learn.git blog/themes/hugo-theme-learn 当然, 最后一步是去 config.toml 中配置一下主题.\ntheme = \u0026quot;hugo-theme-learn\u0026quot; 添加内容 这个主题下, 内容结构大致是这样的.\ncontent 下的每个目录都是一个章节, 每个章节都有一个 _index.md 文件, 当作是章节的介绍. 然后, 就是普通的文章了.\n创建章节 ./bin/hugo new 1-hugo/_index.md --kind chapter -s ./blog 这里的主要命令格式是 hugo new chapter/page.md, new 后面的参数是文章的位置, 位置是相对于 content 目录的. --kind chapter 用来指示这应该生成哪种类型的文件, 这里是 chapter. -s blog 很常见了, 因为我们的 hugo 站点的根目录是在 blog 里的, 所以需要指定源目录.\n创建内容页 ./bin/hugo new 1-hugo/install.md -s ./blog 基本和上面创建章节页是差不多的, 主要是少了 --kind chapter 参数. 默认创建的是内容页.\n创建自定义的页面 其实, 生成各种类型的页面都是自定义的, 具体内容是在 archetypes 目录下. 当然, 具体怎么将布局和页面类型结合起来还是要研究下.\n另一点非常重要的是文章的排序. 可以使用 weight 排序, 定义在最上面的配置块中.\n短代码 短代码是为了弥补 markdown 的缺憾而将一些常见的操作表示为自定义格式的简单代码段. 这些操作通常是没有被 markdown 支持的, 但可以使用 html 实现的. 毕竟 markdown 只是单纯用来写文档的, 你让它支持一些 js 操作就不现实了.\n附件 {{%attachments title=\u0026quot;Related files\u0026quot; pattern=\u0026quot;.*(pdf|mp4)\u0026quot;/%}} 语法看起来是挺简单的, title 是标题, pattern 是正则, 过滤出要显示的文件, style 是样式, 默认的样式包含 “orange”, “grey”, “blue” and “green”.\n当然, 你需要将文件放置在指定的目录中才能被检索到.\n 如果页面是 page.md, 那么目录名为 page.files 如果页面是文件夹, 那么就要建一个子目录 files button {{% button href=\u0026quot;https://getgrav.org/\u0026quot; %}}Get Grav{{% /button %}} {{% button href=\u0026quot;https://getgrav.org/\u0026quot; icon=\u0026quot;fas fa-download\u0026quot; %}}Get Grav with icon{{% /button %}} {{% button href=\u0026quot;https://getgrav.org/\u0026quot; icon=\u0026quot;fas fa-download\u0026quot; icon-position=\u0026quot;right\u0026quot; %}}Get Grav with icon right{{% /button %}} 按钮没什么好说的, 其实就是一个跳转链接, 只是看起来像按钮, 和 html 中的按钮的功能还是差好多.\n子目录 这个还是挺实用的, 能列出下级目录的列表, 用在章节首页是挺好的.\n{{% children %}} mermaid mermaid 也是支持的, 只是我还没仔细看过, 不知道怎么画图. 似乎是用作流程图的神器.\n{{\u0026lt;mermaid align=\u0026quot;left\u0026quot;\u0026gt;}} graph LR; A[Hard edge] --\u0026gt;|Link text| B(Round edge) B --\u0026gt; C{Decision} C --\u0026gt;|One| D[Result one] C --\u0026gt;|Two| E[Result two] {{\u0026lt; /mermaid \u0026gt;}} 支持好多种类型的图, 可以去 官网 观摩一番.\n警告 {{% notice note %}} A notice disclaimer {{% /notice %}} 警告有很多种类型\n note info tip warning 心累 突然发觉要正常显示这些短代码的源代码也是困难的. 最后去看了作者的源码, 有些是空四格, 有些是用 ```.\n"
},
{
"uri": "/",
"title": "Home",
"tags": [],
"description": "",
"content": " tobefan 主页 这是我的个人主页, tobefan.\nwelcome to the future.\n目录 Hugo 安装启动 Hugo New Theme 使用指南 使用 Github Action Docker Kubeadm Helm Draft Linux Mobaxterm Gitlab Bash 脚本使用指南 Go 使用 Go 模块 Template 笔记 Learn 核心数据结构 浙ICP备19044307号-1\n"
},
{
"uri": "/2-docker/",
"title": "Docker",
"tags": [],
"description": "",
"content": " Docker 使用指南 简介 记录学习 docker 的心得.\n"
},
{
"uri": "/1-hugo/github_action/",
"title": "使用 Github Action",
"tags": [],
"description": "",
"content": " 简介 以前也为这个仓库设置过 CI. 当时用的是 buddy. 这个是当时在 github market 中找的, 配置好了用着也挺舒服的.\n唯一有点问题的是, 如果你长期不使用自己创建的 CI 流程, 那么基本上过一会时间, 大概是两三个月, 就会收到一封邮件, 提示如果再不登录的话, 就会自动注销账号.\n在连续好几次这样的操作后, 终于在前段时间, 因为没有使用, 也没有及时查看邮件, 导致被注销了账号.\n所以, 这就引出了这次的主题, Github Action.\n PS. 被微软收购后, 没想到文档都有中文了.\n 使用 当前仓库使用的文件, 可以在 .github/workflows/go.yml 中查看.\nname: Go on: push: branches: [master] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token fetch-depth: 0 # otherwise, you will failed to push refs to dest repo - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.16 - name: Build0 run: chmod 777 ./bin/hugo - name: Build1 run: git submodule init \u0026amp;\u0026amp; git submodule update - name: Build2 run: ./bin/hugo -s ./blog - name: Copy CNAME run: cp ./CNAME ./public - name: Commit run: | cd ./public git init git config --local user.email \u0026quot;[email protected]\u0026quot; git config --local user.name \u0026quot;zhenhua32\u0026quot; git add . git commit -m \u0026quot;init\u0026quot; - name: GitHub Push uses: ad-m/github-push-action@master with: github_token: ${{ secrets.ACCESS_TOKEN }} repository: \u0026quot;zhenhua32/zhenhua32.github.io\u0026quot; branch: master force: true directory: \u0026quot;./public\u0026quot; 基本的概念和通用的 CI CD 流程是相似的,\n 通过 jobs 定义任务列表 在每个 job 中通过 on 定义触发条件 在 steps 定义运行的步骤 问题 在使用的过程中, 也遇到了一些问题.\n推送到另一个仓库 因为我使用了两个仓库来构建 github page. 当前仓库是原始文本存储库, 每当写完一篇文章后 会使用 hugo 重新生成静态文件, 然后将静态文件提交到另一个仓库中.\n这个过程中, 就需要将生成的代码推送到另一个仓库了.\n遇到了两个问题:\n push 时提示没有权限 directory 参数没有效果 Github Action 中有很多别人已经构建好的 action 可以使用, 这里使用了 ad-m/github-push-action@master.\n一个问题是需要使用密钥才能访问另一个仓库, 所以不能直接使用 ${{ secrets.GITHUB_TOKEN }}, 而是要用 personal access token\n生成完个人密钥后, 要将它添加到环境变量中, 就是添加到运行 action 的仓库中. 具体可以参考这个链接, 为仓库创建加密密钥\n注意环境变量名不能以GITHUB_ 开头.\n配置完成后, 就可以更新 github_token 参数了.\n令人沮丧的是, 当时我配置完成后, 每次运行都提示没有权限访问另一个仓库.\nPush to branch master Missing input \u0026quot;github_token: ${{ secrets.GITHUB_TOKEN }}\u0026quot;. Error: Invalid exit code: 1 at ChildProcess.\u0026lt;anonymous\u0026gt; (/home/runner/work/_actions/ad-m/github-push-action/master/start.js:29:21) at ChildProcess.emit (events.js:210:5) at maybeClose (internal/child_process.js:1021:16) at Process.ChildProcess._handle.onexit (internal/child_process.js:283:5) { code: 1 } Error: Invalid exit code: 1 at ChildProcess.\u0026lt;anonymous\u0026gt; (/home/runner/work/_actions/ad-m/github-push-action/master/start.js:29:21) at ChildProcess.emit (events.js:210:5) at maybeClose (internal/child_process.js:1021:16) at Process.ChildProcess._handle.onexit (internal/child_process.js:283:5) 后来, 终于在 issue 中翻到了问题根源. 原来是 actions/checkout@v2 的问题. actions/checkout@v2 的坑\n- uses: actions/checkout@v2 with: persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token fetch-depth: 0 # otherwise, you will failed to push refs to dest repo 更新后, 就能运行成功了.\n另一个问题是, 虽然设置了 directory 参数, 但是直接把当前仓库都推送过去了, 而不是推送了指定目录下的问价.\n这个问题简单些, 就是没有在目录上初始化 git, 所以把上级目录的 git 仓库推送过去了.\n在 push 步骤之前, 运行下初始化仓库就行.\n- name: Commit run: | cd ./public git init git config --local user.email \u0026quot;[email protected]\u0026quot; git config --local user.name \u0026quot;zhenhua32\u0026quot; git add . git commit -m \u0026quot;init\u0026quot; "
},
{
"uri": "/3-linux/",
"title": "Linux",
"tags": [],
"description": "",
"content": " linux 简介 这里主要记录我使用 linxu 或 windows 的经验, 各种零碎的知识.\n"
},
{
"uri": "/2-docker/draft/",
"title": "Draft",
"tags": [],
"description": "",
"content": " draft 简介 draft 是一个帮助开发者在 kubernetes 上创建云原生应用的工具, 简化了应用开发和部署的过程.\n这是我在 vscode 上安装 kubernetes 相关插件时看到的.\n最近好像没有更新, 还是停留在 18 年的 0.16 版本.\n安装 去 github 上的 release 页面 下载二进制文件.\n# 初始化 draft init # 创建应用 draft create # 部署应用 draft up 参考 draft "
},
{
"uri": "/4-go/",
"title": "Go",
"tags": [],
"description": "",
"content": " Go 简介 记录学习 Go 的旅程.\n"
},
{
"uri": "/3-linux/bash/",
"title": "Bash 脚本使用指南",
"tags": [],
"description": "",
"content": " 简介 Bash 脚本在 linux 上还是非常有用的. 以前没有仔细用过, 这些结合教程, 好好学习了一遍, 顺便做些笔记, 也当作自己的快速参考指南.\n当前所看的教程是 Bash 脚本教程.\nShell Shell 可以理解为与内核交互的环境, 同时也是一个命令解释器.\n查看当前时使用的 Shell\necho $SHELL 查看当前系统里已安装的所有 Shell\ncat /etc/shells 进入和退出 Bash\nbash exit 查看 Bash 版本\nbash --version echo $BASH_VERSION 基础语法 echo 是一个非常基础的输出命令, 用于在屏幕上输出一行文本.\n一些常见的用法如下:\n# 常见用法 echo hello world # -n 取消行尾的换行符 echo -n hello world # -e 解释引号中的特殊字符 echo -e \u0026quot;hello\\nworld\u0026quot; Bash 使用空格(或 Tab)区分不同的参数. 使用分号 ; 作为命令的结束符. 借助分号可以在一行中使用多个命令. 命令组合符 \u0026amp;\u0026amp; 和 || 可以控制多个命令的发生关系. Command1 \u0026amp;\u0026amp; Command2 在第一个命令成功后, 继续运行第二个命令 Command1 || Command2 在第一个命令失败后, 继续运行第二个命令 命令有很多类型, 有些是内置命令, 有些是外部程序. 使用 type 获取命令来源. Bash 中的一些快捷键, 非常基础且实用, 建议速记:\n Ctrl + L 清除屏幕并将当前行移到页面顶部。 Ctrl + C 中止当前正在执行的命令。 Shift + PageUp 向上滚动。 Shift + PageDown 向下滚动。 Ctrl + U 从光标位置删除到行首。 Ctrl + K 从光标位置删除到行尾。 Ctrl + D 关闭 Shell 会话。 ↑,↓ 浏览已执行命令的历史记录。 模式扩展 模式扩展是指 Bash 会将用户命令中的特殊字符扩展为完整的定义, 然后才继续执行对应的命令. 类似于简化版的 正则表达式.\nBash 共有 8 种扩展:\n ~ 字符扩展 ? 字符扩展 * 字符扩展 方括号扩展 大括号扩展 变量扩展 子命令扩展 算术扩展 在 Bash 中打开或关闭扩展\n# 打开模式扩展 set +o noglob set +f # 关闭扩展 set -o noglob set -f ~ 会自动扩展为当前用户的主目录 ? 表示文件路径中的任意单个字符, 不包括空格 * 表示文件路径中的任意数量的任意字符, 包括零个字符 [...] 方括号扩展匹配括号中的任意一个字符 [start-end] 方括号扩展的简写形式, 表示一个连续的范围 {.,.,.} 大括号扩展表示大括号中的所有值, 使用逗号分隔值. 逗号前后不能有空格. 可以用于多字符的匹配. {start..end} 大括号的简写模式, 表示一个连续的范围. 支持逆序. Bash 将以 $ 开头的词元视为变量. 变量也可以用 %{} 形式. $(...) 可以扩展为另一个命令的运行结果. $((...)) 可以扩展为整数运行的结果. 用于文件路径匹配时, 需要文件确实存在时才会扩展. 否则会原样输出 文件名扩展时只适用于单层路径, 不能跨目录匹配. Bash4.0 之后可以用 ** 匹配零个或多个子目录.\n一些示例:\necho ~ ls ?.txt ls *.txt ls [ab].txt ls [a-c].txt echo {1,2,3} echo {a..c} echo $SHELL echo $(date) echo $((2 + 2)) [[:class:]] 表示一个字符类. 常用的如下:\n [[:alnum:]] 匹配任意英文字母与数字 [[:alpha:]] 匹配任意英文字母 [[:blank:]] 空格和 Tab 键。 [[:cntrl:]] ASCII 码 0-31 的不可打印字符。 [[:digit:]] 匹配任意数字 0-9。 [[:graph:]] A-Z、a-z、0-9 和标点符号。 [[:lower:]] 匹配任意小写字母 a-z。 [[:print:]] ASCII 码 32-127 的可打印字符。 [[:punct:]] 标点符号(除了 A-Z、a-z、0-9 的可打印字符)。 [[:space:]] 空格、Tab、LF(10)、VT(11)、FF(12)、CR(13) [[:upper:]] 匹配任意大写字母 A-Z。 [[:xdigit:]] 16 进制字符(A-F、a-f、0-9)。 量词语法用于控制模式匹配的次数, 只有在 Bash 的 extglob 参数打开的情况下使用.\n ?(pattern-list) 匹配零个或一个模式。 *(pattern-list) 匹配零个或多个模式。 +(pattern-list) 匹配一个或多个模式。 @(pattern-list) 只匹配一个模式。 !(pattern-list) 匹配零个或一个以上的模式,但不匹配单独一个的模式。 shopt 可以调整 Bash 的行为.\n# 打开某个参数 $ shopt -s [optionname] # 关闭某个参数 $ shopt -u [optionname] # 查询某个参数关闭还是打开 $ shopt [optionname] dotglob 扩展结果包含隐藏文件(以点开头的文件) nullglob 通配符不匹配任何文件名时返回空字符串 failglob 通配符不匹配任何文件名是, 直接报错 extglob 使得 Bash 支持一些 ksh 的扩展语法 nocaseglob 让通配符匹配不区分大小写 globstar 使用 ** 可以匹配零个或多个子目录 引号和转义 使用 \\ 进行转义. 单引号用于保留字符的字面含义, 各种特殊字符在单引号中都会变成普通字符 双引号会保留大部分特殊字符的含义, 但除了美元符 $, 反引号 `, 和反斜杆 \\ 会被自动扩展. 使用 Here 文档 可以输入多行字符串.\n\u0026lt;\u0026lt; token text text token 其中 \u0026lt;\u0026lt; token 是开始标记, token 是结束标记. Here 文档中会发生变量替换, 同时支持反斜杆转义, 但不支持通配符扩展. \u0026lt;\u0026lt; token 中的空格可有可无.\nHere 文档的一个变体是 \u0026lt;\u0026lt;\u0026lt;, 将字符串通过标准输入传递给命令.\ncat \u0026lt;\u0026lt;\u0026lt; 'hi there' 变量 使用 env 或 printenv 显示环境变量\n声明变量\nvariable=value myvar=\u0026quot;hello world\u0026quot; 读取变量: 在变量名前加上 % 字符\nfoo=bar echo $foo 删除变量, 使用 unset.\nunset NAME 输出变量, 使用 export. export 用于向子 Shell 输出环境变量.\nNAME=foo export NAME 特殊变量:\n $? 上一个命令的退出码 $$ 当前 Shell 的进程 ID $_ 上一个命令的最后一个参数 $! 最近一个后台执行的异步命令的进程 ID $0 当前 Shell 的名称或脚本名 $- 当前 Shell 的启动参数 $@ 和 $# 脚本的参数数量 $1 - $9 表示脚本的第 1 - 9 个参数 设置变量的默认值:\n ${varname:-word} varname 存在且不为空返回 varname, 否则返回 word. (保证有默认值兜底) ${varname:=word} varname 存在且不为空返回 varname, 否则将它设置为 word 并返回 word. (设置变量的默认值) ${varname:+word} varname 存在且不为空返回 word, 否则返回空值. (测试变量是否存在) ${varname:?message} varname 存在且不为空返回 varname, 否则打印出 varname:message 并中断脚本执行 (防止变量未定义) 一些其他用于声明变量的命令:\n declare 声明特殊类型的变量. declare OPTION VARIABLE=value -a 声明数组变量。 -f 输出所有函数定义。 -F 输出所有函数名。 -i 声明整数变量。 -l 声明变量为小写字母。 -p 查看变量信息。 -r 声明只读变量。 -u 声明变量为大写字母。 -x 该变量输出为环境变量。 readonly 声明一个只读的变量. let 声明变量时, 可以直接执行算术表达式 字符串操作 ${#varname} 获取字符串长度 ${varname:offset:length} 提取子字符串. offset 从 0 开始计算. 字符串的搜索和替换 ${varname^^} 转换为大写形式 ${varname,,} 转换为小写形式 字符串的搜索和替换 字符串头部的模式匹配 检查字符串的开头是否匹配给定的模式. 匹配成功后会删除匹配成功的部分, 返回剩余的部分. 不改变原始变量.\n# 如果 pattern 匹配变量 variable 的开头, # 删除最短匹配(非贪婪匹配)的部分,返回剩余部分 ${variable#pattern} # 如果 pattern 匹配变量 variable 的开头, # 删除最长匹配(贪婪匹配)的部分,返回剩余部分 ${variable##pattern} 字符串尾部的模式匹配 # 如果 pattern 匹配变量 variable 的结尾, # 删除最短匹配(非贪婪匹配)的部分,返回剩余部分 ${variable%pattern} # 如果 pattern 匹配变量 variable 的结尾, # 删除最长匹配(贪婪匹配)的部分,返回剩余部分 ${variable%%pattern} 任意位置的模式匹配 # 如果 pattern 匹配变量 variable 的一部分, # 最长匹配(贪婪匹配)的那部分被 string 替换,但仅替换第一个匹配 ${variable/pattern/string} # 如果 pattern 匹配变量 variable 的一部分, # 最长匹配(贪婪匹配)的那部分被 string 替换,所有匹配都替换 ${variable//pattern/string} 算术运算 ((...)) 整数的算术运算, 使用 $((...)) 读取算术运算的结果 使用其他进制 number 十进制 0number 八进制 0xnumber 十六进制 base#number base 进制 $((...)) 支持二进制的位运算 \u0026lt;\u0026lt; 位左移运算,把一个数字的所有位向左移动指定的位。 \u0026gt;\u0026gt; 位右移运算,把一个数字的所有位向右移动指定的位。 \u0026amp; 位的“与”运算,对两个数字的所有位执行一个AND操作。 | 位的“或”运算,对两个数字的所有位执行一个OR操作。 ~ 位的“否”运算,对一个数字的所有位取反。 ^ 位的异或运算(exclusive or),对两个数字的所有位执行一个异或操作。 $((...)) 支持的逻辑运算. 如果表达式为真, 返回 1, 否则返回 0. \u0026lt; 小于 \u0026gt; 大于 \u0026lt;= 小于或相等 \u0026gt;= 大于或相等 == 相等 != 不相等 \u0026amp;\u0026amp; 逻辑与 || 逻辑或 ! 逻辑否 expr1?expr2:expr3 三元条件运算符。若表达式expr1的计算结果为非零值(算术真),则执行表达式expr2,否则执行表达式expr3。 $((...)) 可以执行赋值运算. parameter = value 简单赋值。 parameter += value 等价于parameter = parameter + value。 parameter -= value 等价于parameter = parameter – value。 parameter *= value 等价于parameter = parameter * value。 parameter /= value 等价于parameter = parameter / value。 parameter %= value 等价于parameter = parameter % value。 parameter \u0026lt;\u0026lt;= value 等价于parameter = parameter \u0026lt;\u0026lt; value。 parameter \u0026gt;\u0026gt;= value 等价于parameter = parameter \u0026gt;\u0026gt; value。 parameter \u0026amp;= value 等价于parameter = parameter \u0026amp; value。 parameter |= value 等价于parameter = parameter | value。 parameter ^= value 等价于parameter = parameter ^ value。 $((...)) 内逗号,是求值运算符, 执行前后两个表达式, 并返回后一个表达式的值. expr 命令支持算术运算, 可以不使用 ((...)). 行操作 这一节可以极大加速命令行使用中的操作速度, 建议速记.\nBash 内置了 Readline 库, 提供了很多行操作.\n光标移动\n Ctrl + a 移到行首。 Ctrl + b 向行首移动一个字符,与左箭头作用相同。 Ctrl + e 移到行尾。 Ctrl + f 向行尾移动一个字符,与右箭头作用相同。 Alt + f 移动到当前单词的词尾。 Alt + b 移动到当前单词的词首。 清除屏幕使用 Ctrl + l, 和 clear 命令作用相同.\n编辑操作\n Ctrl + d 删除光标位置的字符(delete)。 Ctrl + w 删除光标前面的单词。 Ctrl + t 光标位置的字符与它前面一位的字符交换位置(transpose)。 Alt + t 光标位置的词与它前面一位的词交换位置(transpose)。 Alt + l 将光标位置至词尾转为小写(lowercase)。 Alt + u 将光标位置至词尾转为大写(uppercase)。\n Ctrl + k 剪切光标位置到行尾的文本。\n Ctrl + u 剪切光标位置到行首的文本。\n Alt + d 剪切光标位置到词尾的文本。\n Alt + Backspace 剪切光标位置到词首的文本。\n Ctrl + y 在光标位置粘贴文本。\n Tab 完成自动补全。\n Alt + ? 列出可能的补全,与连按两次 Tab 键作用相同。\n Alt + / 尝试文件路径补全。\n Ctrl + x / 先按Ctrl + x,再按/,等同于Alt + ?,列出可能的文件路径- 补全。\n Alt + ! 命令补全。\n Ctrl + x ! 先按Ctrl + x,再按!,等同于Alt + !,命令补全。\n Alt + ~ 用户名补全。\n Ctrl + x ~先按Ctrl + x,再按~,等同于Alt + ~,用户名补全。\n Alt + $ 变量名补全。\n Ctrl + x $ 先按Ctrl + x,再按$,等同于Alt + $,变量名补全。\n Alt + @ 主机名补全。\n Ctrl + x @ 先按Ctrl + x,再按@,等同于Alt + @,主机名补全。\n Alt + * 在命令行一次性插入所有可能的补全。\n Alt + Tab 尝试用.bash_history里面以前执行命令,进行补全。\n 操作历史 Bash 会保留用户的操作历史, 退出 Shell 式, 会将操作历史写入都 ~/.bash_history 中.\necho $HISTFILE !e 表示找出操作历史中, 最近的一条以 e 开头的命令并执行. Bash 会先输出那条命令, 然后直接执行.\nhistory 会显示操作历史. 可以通过定制 HISTTIMEFORMAT 来显示每个操作的时间.\n Ctrl + p 显示上一个命令,与向上箭头效果相同(previous)。 Ctrl + n 显示下一个命令,与向下箭头效果相同(next)。 Alt + \u0026lt; 显示第一个命令。 Alt + \u0026gt; 显示最后一个命令,即当前的命令。 Ctrl + o 执行历史文件里面的当前条目,并自动显示下一条命令。这对重复执行某个序列的命令很有帮助。\n !! 执行上一个命令。\n !n 执行历史文件里面行号为n的命令。\n !-n 执行当前命令之前n条的命令。\n !string 执行最近一个以指定字符串string开头的命令。\n !?string 执行最近一条包含字符串string的命令。\n ^string1^string2 执行最近一条包含string1的命令,将其替换成string2。\n Ctrl + j 等同于回车键(LINEFEED)。\n Ctrl + m 等同于回车键(CARRIAGE RETURN)。\n Ctrl + o 等同于回车键,并展示操作历史的下一个命令。\n Ctrl + v 将下一个输入的特殊字符变成字面量,比如回车变成^M。\n Ctrl + [ 等同于 ESC。\n Alt + . 插入上一个命令的最后一个词。\n Alt + _ 等同于Alt + .。\n 目录堆栈 cd - 进度前一次的目录 pushd 进入目录, 并将该目录放入堆栈 popd 移除堆栈顶部的记录, 并进入新的堆栈顶部目录 dirs 显示目录堆栈的内容 pushd 和 popd 支持的参数:\n -n 仅操作堆栈, 不改变目录 整数参数 该整数表示堆栈中的指定位置的记录. 从 0 开始. 目录参数 pushd 可以接受一个目录作为参数, 表示将目录放到堆栈顶部, 并进入该目录 脚本入门 脚本的第一行通常是指定解释器. 这一行以 #! 开头, 这个字符被称为 Shebang.\n#!/bin/bash 脚本通常需要执行权限, 使用 chmod 添加权限\nchmod +x script.sh 如果需要脚本在任何地方都能执行,必须加到环境变量 $PATH 中. 比如将脚本统一放在 ~/bin 目录, 然后将其加入到 $PATH 中.\nexport PATH=$PATH:~/bin # 可以将上面这行加入到 ~/.bashrc 中, 然后运行下面这句使得生效 source ~/.bashrc #!/usr/bin/env NAME 让 Shell 查找环境变量中第一个匹配的 NAME.\n使用 # 表示注释.\n脚本参数 调用脚本时, 脚本后面可以带有参数.\n $0 脚本文件名,即script.sh。 $1~$9 对应脚本的第一个参数到第九个参数。 $# 参数的总数。 $@ 全部的参数,参数之间使用空格分隔。 $* 全部的参数,参数之间使用变量$IFS值的第一个字符分隔,默认为空格,但是可以自定义。 ${10} 脚本参数多于 10 以后, 可以用 ${} 形式 shift 可以改变脚本参数, 每次执行都会移除脚本当前的第一个参数, 使得后面的参数前进一位.\ngetopts 可以解析复杂的脚本命令行参数, 获取所有带前置连词线 - 的参数.\ngetopts optstring name optstring 给出脚本的所有连词线参数. 后面有冒号的表示有参数值. name 是一个变量名, 用来保存当前提取到的配置项参数.\nwhile getopts 'lha:' OPTION; do case \u0026quot;$OPTION\u0026quot; in l) echo \u0026quot;linuxconfig\u0026quot; ;; h) echo \u0026quot;h stands for h\u0026quot; ;; a) avalue=\u0026quot;$OPTARG\u0026quot; echo \u0026quot;The value provided is $OPTARG\u0026quot; ;; ?) echo \u0026quot;script usage: $(basename $0) [-l] [-h] [-a somevalue]\u0026quot; \u0026gt;\u0026amp;2 exit 1 ;; esac done shift \u0026quot;$(($OPTIND - 1))\u0026quot; $OPTIND 在开始执行前是 1, 每次执行都会加 1. $OPTIND - 1 是已经处理的连词线参数的个数, 使用 shift 将这些参数移除.\n配置项参数终止符 --, 可以让执行变量只能作为实体参数, 而不能作为配置项参数.\nmyPath=\u0026quot;~/docs\u0026quot; ls -- $myPath exit 用于终止当前脚本的执行, 并向 Shell 返回一个退出值.\n命令执行结束后, 有一个返回值. 0 表示成功, 非 0 表示失败. 用 $? 可以读取前一个命令的返回值.\nsource 用于执行一个脚本, 通常用于重载一个配置文件.\nalias 可以为一个命令执行别名.\n# 指定别名 alias NAME=DEFINITION # 列出所有别名 alias # 解除别名 unalias lt "
},
{
"uri": "/5-learn/",
"title": "Learn",
"tags": [],
"description": "",
"content": " Learn 简介 记录一些学习上的笔记.\n"
},
{
"uri": "/categories/",
"title": "Categories",
"tags": [],
"description": "",
"content": ""
},
{
"uri": "/tags/",
"title": "Tags",
"tags": [],
"description": "",
"content": ""
}]