跳转至

2024

约 1 个字 预计阅读时间不到 1 分钟

AstroMailer 🌌

约 509 个字 29 行代码 1 张图片 预计阅读时间 2 分钟

AstroMailer 是一个每日早安邮件发送器,它通过集成 NASA 天文每日一图(APOD)、词霸金句和百度翻译 API,为用户带来浪漫、温暖的早安邮件。

github地址: AstroMailer

功能概述

  • 📧 每日发送早安邮件
    • 附带 NASA 天文每日一图及其解释。
    • 包含词霸每日金句(中英双语)。
    • 邮件支持中英文翻译,并使用简洁优雅的 HTML 样式设计。
  • 🌌 随机背景色和样式
    • 邮件中的句子和背景配色采用动态随机生成,让每一天的邮件都有独特的风格。
    • 🔄 缓存机制
    • 使用 diskcache 缓存 NASA 图片和内容,减少重复 API 请求。

项目架构

AstroMailer 的主要功能分为以下模块:

  1. 邮件发送模块
    • 使用 smtplib 实现邮件的发送。
    • 支持 HTML 格式化邮件内容。
  2. API 调用模块
    • 集成 NASA APOD API 获取每日天文图片和描述。
    • 集成百度翻译 API 翻译图片描述为中文。
    • 使用词霸 API 获取每日金句。
  3. 定时任务模块
    • 使用 schedule 实现每天早上 8 点自动发送邮件。
  4. 缓存模块
    • 使用 diskcache 缓存数据,避免频繁调用 API。

运行截图

📧 邮件示例

技术栈

  • 编程语言: Python 3.10+

  • 主要依赖:

    • smtplib: 发送邮件
    • schedule: 定时任务调度
    • diskcache: 本地缓存
    • requests: 调用 API
    • PyYAML: 解析配置文件
  • API:

安装与运行

1. 克隆项目

Bash
git clone https://github.com/Pale-illusions/AstroMailer.git
cd AstroMailer

2. 创建虚拟环境并安装依赖

Bash
1
2
3
4
5
6
7
8
9
# 创建虚拟环境
python3 -m venv myvenv

# 激活虚拟环境
source myvenv/bin/activate  # macOS/Linux
myvenv\Scripts\activate     # Windows

# 安装依赖
pip install -r requirements.txt

3. 配置环境

resources/config.yaml 文件中配置你的 API 密钥和邮件账户信息。

示例配置文件 config.yaml

YAML
api_key:
  nasa: "你的NASA_API_KEY"
  baidu_translate:
    appid: "你的百度翻译APPID"
    secret_key: "你的百度翻译SECRET_KEY"

email:
  name: "AstroMailer"
  smtp_server: "smtp.gmail.com"
  smtp_port: 587
  sender: "your_email@example.com"
  password: "your_email_password"
  recipients:
    - "recipient1@example.com"
    - "recipient2@example.com"

4. 启动服务

直接运行

Bash
python src/main.py

docker

Bash
docker-compose up -d

贡献指南

欢迎贡献代码!如果你想为 AstroMailer 提交新的功能或修复 bug,请按照以下步骤操作:

  1. Fork 仓库。
  2. 创建新分支:
Bash
git checkout -b feature/your-feature-name
  1. 提交代码并创建 Pull Request。

开源协议

本项目采用 Apache-2.0 license


感谢使用 AstroMailer,让宇宙的浪漫点亮你的每一天!✨

Editors (Vim)

约 1214 个字 110 行代码 1 张图片 预计阅读时间 5 分钟

Attachment

course link: Editors (Vim)

Vim has multiple operating modes.

  • Normal: for moving around a file and making edits
  • Insert: for inserting text
  • Replace: for replacing text
  • Visual (plain, line, or block): for selecting blocks of text
  • Command-line: for running a command

Basics

Inserting text

From Normal mode, press i to enter Insert mode.

Now, Vim behaves like any other text editor, until you press<ESC> to return to Normal mode.


Buffers, tabs, and windows

Vim maintains a set of open files, called “buffers”.

A Vim session has a number of tabs, each of which has a number of windows (split panes).

Each window shows a single buffer. Unlike other programs you are familiar with, like web browsers, there is not a 1-to-1 correspondence between buffers and windows; windows are merely views.

A given buffer may be open in multiple windows, even within the same tab.

This can be quite handy, for example, to view two different parts of a file at the same time.

By default, Vim opens with a single tab, which contains a single window.


Command-line

Command mode can be entered by typing : in Normal mode.

Your cursor will jump to the command line at the bottom of the screen upon pressing :.

This mode has many functionalities, including opening, saving, and closing files, and quitting Vim.

  • :q quit (close window)
  • :w save (“write”)
  • :wq save and quit
  • :e {name of file} open file for editing
  • :ls show open buffers
  • :help {topic} open help
    • :help :w opens help for the :w command
    • :help w opens help for the w movement

Vim’s interface is a programming language

Movement

Movements in Vim are also called “nouns”, because they refer to chunks of text.

  • Basic movement: hjkl (left, down, up, right)
  • Words: w (next word), b (beginning of word), e (end of word)
  • Lines: 0 (beginning of line), ^ (first non-blank character), $ (end of line)
  • Screen: H (top of screen), M (middle of screen), L (bottom of screen)
  • Scroll: Ctrl-u (up), Ctrl-d (down)
  • File: gg (beginning of file), G (end of file)
  • Line numbers: :{number}<CR> or {number}G (line {number})
  • Misc: % (corresponding item)
  • Find: f{character}, t{character}, F{character}, T{character}
    • find/to forward/backward {character} on the current line
    • , / ; for navigating matches
  • Search: /{regex}, n / N for navigating matches

Selection

Visual modes:

  • Visual: v
  • Visual Line: V
  • Visual Block: Ctrl-v

Can use movement keys to make selection.


Edits

Vim’s editing commands are also called “verbs”, because verbs act on nouns.

  • i enter Insert mode
    • but for manipulating/deleting text, want to use something more than backspace
  • o / O insert line below / above
  • d{motion} delete {motion}
    • e.g. dw is delete word, d$ is delete to end of line, d0 is delete to beginning of line
  • c{motion} change {motion}
    • e.g. cw is change word
    • like d{motion} followed by i
  • x delete character (equal do dl)
  • s substitute character (equal to cl)
  • Visual mode + manipulation
    • select text, d to delete it or c to change it
  • u to undo, <C-r> to redo
  • y to copy / “yank” (some other commands like d also copy)
  • p to paste
  • Lots more to learn: e.g. ~ flips the case of a character

Counts

You can combine nouns and verbs with a count, which will perform a given action a number of times.

  • 3w move 3 words forward
  • 5j move 5 lines down
  • 7dw delete 7 words

Modifiers

You can use modifiers to change the meaning of a noun.

Some modifiers are i, which means “inner” or “inside”, and a, which means “around”.

  • ci( change the contents inside the current pair of parentheses
  • ci[ change the contents inside the current pair of square brackets
  • da' delete a single-quoted string, including the surrounding single quotes

Customizing Vim

Vim is customized through a plain-text configuration file in ~/.vimrc (containing Vimscript commands).

Here is the basic configuration file that provided by this course.

VimL
" Comments in Vimscript start with a `"`.

" If you open this file in Vim, it'll be syntax highlighted for you.

" Vim is based on Vi. Setting `nocompatible` switches from the default
" Vi-compatibility mode and enables useful Vim functionality. This
" configuration option turns out not to be necessary for the file named
" '~/.vimrc', because Vim automatically enters nocompatible mode if that file
" is present. But we're including it here just in case this config file is
" loaded some other way (e.g. saved as `foo`, and then Vim started with
" `vim -u foo`).
set nocompatible

" Turn on syntax highlighting.
syntax on

" Disable the default Vim startup message.
set shortmess+=I

" Show line numbers.
set number

" This enables relative line numbering mode. With both number and
" relativenumber enabled, the current line shows the true line number, while
" all other lines (above and below) are numbered relative to the current line.
" This is useful because you can tell, at a glance, what count is needed to
" jump up or down to a particular line, by {count}k to go up or {count}j to go
" down.
set relativenumber

" Always show the status line at the bottom, even if you only have one window open.
set laststatus=2

" The backspace key has slightly unintuitive behavior by default. For example,
" by default, you can't backspace before the insertion point set with 'i'.
" This configuration makes backspace behave more reasonably, in that you can
" backspace over anything.
set backspace=indent,eol,start

" By default, Vim doesn't let you hide a buffer (i.e. have a buffer that isn't
" shown in any window) that has unsaved changes. This is to prevent you from "
" forgetting about unsaved changes and then quitting e.g. via `:qa!`. We find
" hidden buffers helpful enough to disable this protection. See `:help hidden`
" for more information on this.
set hidden

" This setting makes search case-insensitive when all characters in the string
" being searched are lowercase. However, the search becomes case-sensitive if
" it contains any capital letters. This makes searching more convenient.
set ignorecase
set smartcase

" Enable searching as you type, rather than waiting till you press enter.
set incsearch

" Unbind some useless/annoying default key bindings.
nmap Q <Nop> " 'Q' in normal mode enters Ex mode. You almost never want this.

" Disable audible bell because it's annoying.
set noerrorbells visualbell t_vb=

" Enable mouse support. You should avoid relying on this too much, but it can
" sometimes be convenient.
set mouse+=a

" Try to prevent bad habits like using the arrow keys for movement. This is
" not the only possible bad habit. For example, holding down the h/j/k/l keys
" for movement, rather than using more efficient movement commands, is also a
" bad habit. The former is enforceable through a .vimrc, while we don't know
" how to prevent the latter.
" Do this in normal mode...
nnoremap <Left>  :echoe "Use h"<CR>
nnoremap <Right> :echoe "Use l"<CR>
nnoremap <Up>    :echoe "Use k"<CR>
nnoremap <Down>  :echoe "Use j"<CR>
" ...and in insert mode
inoremap <Left>  <ESC>:echoe "Use h"<CR>
inoremap <Right> <ESC>:echoe "Use l"<CR>
inoremap <Up>    <ESC>:echoe "Use k"<CR>
inoremap <Down>  <ESC>:echoe "Use j"<CR>

For macos, There might not have the default .vimrc file in ~.

But never mind, just type vim ~/.vimrc and paste the code into it, then type :wq.

You will find that everything is done!

Extending Vim

copy lecture notes...need review

There are tons of plugins for extending Vim. Contrary to outdated advice that you might find on the internet, you do not need to use a plugin manager for Vim (since Vim 8.0). Instead, you can use the built-in package management system. Simply create the directory ~/.vim/pack/vendor/start/, and put plugins in there (e.g. via git clone).

Here are some of our favorite plugins:

We’re trying to avoid giving an overwhelmingly long list of plugins here. You can check out the instructors’ dotfiles (Anish, Jon, Jose) to see what other plugins we use. Check out Vim Awesome for more awesome Vim plugins. There are also tons of blog posts on this topic: just search for “best Vim plugins”.

Resources

Exercises

Warning

I will write my own solution to the problem, If you want to solve those by yourself, please stop.


Problem1

Complete vimtutor. Note: it looks best in a 80x24 (80 columns by 24 lines) terminal window.


Solution

Bash
vimtutor

Problem2

  • Install and configure a plugin: ctrlp.vim.

    1. Create the plugins directory with mkdir -p ~/.vim/pack/vendor/start
    2. Download the plugin: cd ~/.vim/pack/vendor/start; git clone https://github.com/ctrlpvim/ctrlp.vim
    3. Read the documentation for the plugin. Try using CtrlP to locate a file by navigating to a project directory, opening Vim, and using the Vim command-line to start :CtrlP.
    4. Customize CtrlP by adding configuration to your ~/.vimrc to open CtrlP by pressing Ctrl-P.


Solution

...TO DO


Problem3

To practice using Vim, re-do the Demo from lecture on your own machine.

Here is a broken fizz buzz implementation:

Python
def fizz_buzz(limit):
    for i in range(limit):
        if i % 3 == 0:
            print('fizz')
        if i % 5 == 0:
            print('fizz')
        if i % 3 and i % 5:
            print(i)

def main():
    fizz_buzz(10)

fix the following issues:

  • Main is never called
  • Starts at 0 instead of 1
  • Prints “fizz” and “buzz” on separate lines for multiples of 15
  • Prints “fizz” for multiples of 5
  • Uses a hard-coded argument of 10 instead of taking a command-line argument


Solution

  1. G move to the bottom of the file
  2. o create a new line
  3. insert code:
    Python
    if __name__ == '__main__':
        main()
    
  4. /limit search for limit, then press n to move to the next match
  5. i enter insert mode, and change limit to 1, limit + 1
  6. /fizz search for fizz, then press n to move to the next match.
  7. ci' change the inner text of ', insert buzz
  8. move the cursor to the print('fizz') line, press $, then i enter insert mode and insert , end=''
  9. jj. move the cursor to line 6 and make the same change as previous did
  10. gg move the cursor to the top of the file, O create the new line above,and insert import sys
  11. /10 find 10, then ci( change the text in (, type int(sys.argv[1])

The final result:

Python
import sys

def fizz_buzz(limit):
    for i in range(1, limit + 1):
        if i % 3 == 0:
            print('fizz', end='')
        if i % 5 == 0:
            print('buzz', end='')
        if i % 3 and i % 5:
            print(i)

def main():
    fizz_buzz(int(sys.argv[1]))

if __name__ == '__main__':
    main()


Problem4

(Advanced) Convert XML to JSON (example file) using Vim macros. Try to do this on your own, but you can look at the macros section above if you get stuck.


Solution

...TO DO

Homebrew

约 19 个字 2 行代码 预计阅读时间不到 1 分钟

执行以下代码,跟着配置就行

Bash
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"

关闭自动更新

Bash
echo 'export HOMEBREW_NO_AUTO_UPDATE=1' >> ~/.zshrc

oh-my-zsh

约 337 个字 6 行代码 5 张图片 预计阅读时间 1 分钟

本文基于 macos 系统,使用 oh-my-zsh 对终端进行美化。

Oh My Zsh 是基于 zsh 命令行的一个扩展工具集,提供了丰富的扩展功能。

1. 安装 oh-my-zsh

Bash
sh -c "$(curl -fsSL https://install.ohmyz.sh/)"

2. 配置主题

使用 powerlevel10k 主题。

Bash
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k

~/.zshrc 设置 ZSH_THEME="powerlevel10k/powerlevel10k"

运行命令

Bash
source ~/.zshrc

接下来,终端会自动引导你配置 powerlevel10k

如果希望重新配置,输入以下命令

Bash
p10k configure

3. 安装插件

3.1 zsh -autosuggestions

zsh -autosuggestions 是一个命令提示插件,当你输入命令时,会自动推测你可能需要输入的命令,按下右键可以快速采用建议。效果如下:

安装方式:把插件下载到本地的 ~/.oh-my-zsh/custom/plugins 目录

Bash
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions

3.2 zsh-syntax-highlighting

zsh-syntax-highlighting 是一个命令语法校验插件,在输入命令的过程中,若指令不合法,则指令显示为红色,若指令合法就会显示为绿色。效果如下:

安装方式:把插件下载到本地的 ~/.oh-my-zsh/custom/plugins 目录。

Bash
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting

3.3 z

oh-my-zsh 内置了 z 插件。z 是一个文件夹快捷跳转插件,对于曾经跳转过的目录,只需要输入最终目标文件夹名称,就可以快速跳转,避免再输入长串路径,提高切换文件夹的效率。效果如下:

3.4 启用插件

修改 ~/.zshrc 中插件列表为:

Text Only
plugins=(git zsh-syntax-highlighting zsh-autosuggestions z)

执行 source ~/.zshrc

Shell Tools and Scripting

约 2038 个字 149 行代码 预计阅读时间 9 分钟

Attachment

course link: Shell Tools and Scripting

Shell Scripting

1. Assign Variables

To assign variables in bash, use the syntax foo=bar and access the value of the variable with $foo. Note that foo = bar will not work since it is interpreted as calling the foo program with arguments = and bar. In general, in shell scripts the space character will perform argument splitting. This behavior can be confusing to use at first, so always check for that.

Strings in bash can be defined with ' and " delimiters.

Strings delimited with ' are literal strings and will not substitute variable values.

Strings delimited with " will substitute variable values.

Bash
1
2
3
4
5
foo=bar
echo "$foo"
# prints bar
echo '$foo'
# prints $foo

2. Functions

As with most programming languages, bash supports control flow techniques including if, case, while and for. Similarly, bash has functions that take arguments and can operate with them.

Here is an example of a function that creates a directory and cds into it.

Bash
1
2
3
4
mcd () {
    mkdir -p "$1"
    cd "$1"
}

Here is how to test mcd function

Bash
1
2
3
4
5
6
vim mcd.sh
# paste the code into it...
source mcd.sh
mcd test
# And you will find that you are in the test directory, which is a 
# child dir in which you run the mcd program

3. Function arguments

Unlike other scripting languages, bash uses a variety of special variables to refer to arguments, error codes, and other relevant variables. Below is a list of some of them. A more comprehensive list can be found here.

  • $0 - Name of the script
  • $1 to $9 - Arguments to the script. $1 is the first argument and so on.
  • $@ - All the arguments
  • $# - Number of arguments
  • $? - Return code of the previous command
  • $$ - Process identification number (PID) for the current script
  • !! - Entire last command, including arguments. A common pattern is to execute a command only for it to fail due to missing permissions; you can quickly re-execute the command with sudo by doing sudo !!
  • $_ - Last argument from the last command. If you are in an interactive shell, you can also quickly get this value by typing Esc followed by . or Alt+.

Commands will often return output using STDOUT, errors through STDERR, and a Return Code to report errors in a more script-friendly manner.

The return code or exit status is the way scripts/commands have to communicate how execution went.

  • A value of 0 usually means everything went OK
  • anything different from 0 means an error occurred.

Exit codes can be used to conditionally execute commands using && (and operator) and || (or operator), both of which are short-circuiting operators.

Commands can also be separated within the same line using a semicolon ;.

  • The true program will always have a 0 return code
  • the false command will always have a 1 return code.

Let’s see some examples

Bash
false || echo "Oops, fail"
# Oops, fail

true || echo "Will not be printed"
#

true && echo "Things went well"
# Things went well

false && echo "Will not be printed"
#

true ; echo "This will always run"
# This will always run

false ; echo "This will always run"
# This will always run

4. Substitution

Another common pattern is wanting to get the output of a command as a variable.

This can be done with command substitution. Whenever you place $( CMD ) it will execute CMD, get the output of the command and substitute it in place.

For example, if you do for file in $(ls), the shell will first call ls and then iterate over those values.

A lesser known similar feature is process substitution, <( CMD ) will execute CMD and place the output in a temporary file and substitute the <() with that file’s name.

This is useful when commands expect values to be passed by file instead of by STDIN. For example, diff <(ls foo) <(ls bar) will show differences between files in dirs foo and bar.


let’s see an example that showcases some of these features.

It will iterate through the arguments we provide, grep for the string foobar, and append it to the file as a comment if it’s not found.

Bash
#!/bin/bash

echo "Starting program at $(date)" # Date will be substituted

echo "Running program $0 with $# arguments with pid $$"

for file in "$@"; do
    grep foobar "$file" > /dev/null 2> /dev/null
    # When pattern is not found, grep has exit status 1
    # We redirect STDOUT and STDERR to a null register since we do not care about them
    if [[ $? -ne 0 ]]; then
        echo "File $file does not have any foobar, adding one"
        echo "# foobar" >> "$file"
    fi
done

In the comparison we tested whether $? was not equal to 0. Bash implements many comparisons of this sort - you can find a detailed list in the manpage for test.

When performing comparisons in bash, try to use double brackets [[ ]] in favor of simple brackets [ ]. Chances of making mistakes are lower although it won’t be portable to sh. A more detailed explanation can be found here.


5. Globbing

When launching scripts, you will often want to provide arguments that are similar. Bash has ways of making this easier, expanding expressions by carrying out filename expansion. These techniques are often referred to as shell globbing.

  • Wildcards
    • ? match one character.
    • * match any amount of characters.
    • For instance, given files foo, foo1, foo2, foo10 and bar, the command rm foo? will delete foo1 and foo2 whereas rm foo* will delete all but bar.
  • Curly braces {} - Whenever you have a common substring in a series of commands, you can use curly braces for bash to expand this automatically. This comes in very handy when moving or converting files.
Bash
convert image.{png,jpg}
# Will expand to
convert image.png image.jpg

cp /path/to/project/{foo,bar,baz}.sh /newpath
# Will expand to
cp /path/to/project/foo.sh /path/to/project/bar.sh /path/to/project/baz.sh /newpath

# Globbing techniques can also be combined
mv *{.py,.sh} folder
# Will move all *.py and *.sh files


mkdir foo bar
# This creates files foo/a, foo/b, ... foo/h, bar/a, bar/b, ... bar/h
touch {foo,bar}/{a..h}
touch foo/x bar/y
# Show differences between files in foo and bar
diff <(ls foo) <(ls bar)
# Outputs
# < x
# ---
# > y

6. shebang

Note that scripts need not necessarily be written in bash to be called from the terminal. For instance, here’s a simple Python script that outputs its arguments in reversed order:

Python
1
2
3
4
#!/usr/local/bin/python
import sys
for arg in reversed(sys.argv[1:]):
    print(arg)

The kernel knows to execute this script with a python interpreter instead of a shell command because we included a shebang line at the top of the script.

It is good practice to write shebang lines using the env command that will resolve to wherever the command lives in the system, increasing the portability of your scripts.

btw, if you really want to figure out where your program is, type this: which python, namely which ***, and add the output to the shebang line

To resolve the location, env will make use of the PATH environment variable we introduced in the first lecture. For this example the shebang line would look like #!/usr/bin/env python.

Some differences between shell functions and scripts that you should keep in mind are:

  • Functions have to be in the same language as the shell, while scripts can be written in any language. This is why including a shebang for scripts is important.
  • Functions are loaded once when their definition is read. Scripts are loaded every time they are executed. This makes functions slightly faster to load, but whenever you change them you will have to reload their definition.
  • Functions are executed in the current shell environment whereas scripts execute in their own process. Thus, functions can modify environment variables, e.g. change your current directory, whereas scripts can’t. Scripts will be passed by value environment variables that have been exported using export
  • As with any programming language, functions are a powerful construct to achieve modularity, code reuse, and clarity of shell code. Often shell scripts will include their own function definitions.

Shell Tools

1. Finding how to use commands

  • the first-order approach is to call said command with the -h or --help flags.
  • A more detailed approach is to use the man command. Short for manual, man provides a manual page (called manpage) for a command you specify.
  • For interactive tools such as the ones based on ncurses, help for the commands can often be accessed within the program using the :help command or typing ?.

Sometimes manpages can provide overly detailed descriptions of the commands, making it hard to decipher what flags/syntax to use for common use cases.

TLDR pages are a nifty complementary solution that focuses on giving example use cases of a command so you can quickly figure out which options to use.

To use tldr, just type: tldr *** (just remind, you need to install it first)


2. Finding files

find will recursively search for files matching some criteria.

Some examples:

Bash
1
2
3
4
5
6
7
8
# Find all directories named src
find . -name src -type d
# Find all python files that have a folder named test in their path
find . -path '*/test/*.py' -type f
# Find all files modified in the last day
find . -mtime -1
# Find all zip files with size in range 500k to 10M
find . -size +500k -size -10M -name '*.tar.gz'

Beyond listing files, find can also perform actions over files that match your query. This property can be incredibly helpful to simplify what could be fairly monotonous tasks.

Bash
1
2
3
4
# Delete all files with .tmp extension
find . -name '*.tmp' -exec rm {} \;
# Find all PNG files and convert them to JPG
find . -name '*.png' -exec convert {} {}.jpg \;

3. Finding code

grep, a generic tool for matching patterns from the input text.

For now, know that grep has many flags that make it a very versatile tool. Some I frequently use are -C for getting Context around the matching line and -v for inverting the match, i.e. print all lines that do not match the pattern.

For example, grep -C 5 will print 5 lines before and after the match. When it comes to quickly searching through many files, you want to use -R since it will Recursively go into directories and look for files for the matching string.

But grep -R can be improved in many ways, such as ignoring .git folders, using multi CPU support, &c.

Many grep alternatives have been developed, including ack, ag and rg. All of them are fantastic and pretty much provide the same functionality. For now I am sticking with ripgrep (rg), given how fast and intuitive it is. Some examples:

Bash
1
2
3
4
5
6
7
8
# Find all python files where I used the requests library
rg -t py 'import requests'
# Find all files (including hidden files) without a shebang line
rg -u --files-without-match "^#\!"
# Find all matches of foo and print the following 5 lines
rg foo -A 5
# Print statistics of matches (# of matched lines and files )
rg --stats PATTERN

4. Finding shell commands

The history command will let you access your shell history programmatically.

It will print your shell history to the standard output. If we want to search there we can pipe that output to grep and search for patterns. history | grep find will print commands that contain the substring “find”.

In most shells, you can make use of Ctrl+R to perform backwards search through your history. After pressing Ctrl+R, you can type a substring you want to match for commands in your history. As you keep pressing it, you will cycle through the matches in your history.


5. Directory Navigation

uh...just look up the lecture notes

Exercises

Warning

I will write my own solution to the problem, If you want to solve those by yourself, please stop.


Problem1

Read man ls and write an ls command that lists files in the following manner

  • Includes all files, including hidden files
  • Sizes are listed in human readable format (e.g. 454M instead of 454279954)
  • Files are ordered by recency
  • Output is colorized

A sample output would look like this

Bash
1
2
3
4
5
 -rw-r--r--   1 user group 1.1M Jan 14 09:53 baz
 drwxr-xr-x   5 user group  160 Jan 14 09:53 .
 -rw-r--r--   1 user group  514 Jan 14 06:42 bar
 -rw-r--r--   1 user group 106M Jan 13 12:12 foo
 drwx------+ 47 user group 1.5K Jan 12 18:08 ..


Solution

Bash
[root@iZbp12idmwavjjcx2k19kjZ course2]# ls -a -l -h -t --color=auto
total 88K
-rw-r--r--  1 root root  54K Dec  3 21:46 install_lts.sh
drwxr-xr-x  5 root root 4.0K Dec  3 21:46 .
-rwxrwxrwx  1 root root   83 Dec  3 20:04 script.py
drwxr-xr-x 11 root root 4.0K Dec  3 20:02 bar
drwxr-xr-x 11 root root 4.0K Dec  3 20:02 foo
-rwxrwxrwx  1 root root   50 Dec  3 19:21 mcd.sh
-rwxrwxrwx  1 root root  485 Dec  3 19:21 f.sh
drwxr-xr-x  2 root root 4.0K Dec  3 19:17 test
drwxr-xr-x 14 root root 4.0K Dec  3 19:12 ..
  • -a : includes all files
  • -l : use a long listing format
  • -h : print sizes like 1K 234M 2G etc.
  • -t : sort by recency
  • --color=auto : colorize output

Problem2

Write bash functions marco and polo that do the following. Whenever you execute marco the current working directory should be saved in some manner, then when you execute polo, no matter what directory you are in, polo should cd you back to the directory where you executed marco. For ease of debugging you can write the code in a file marco.sh and (re)load the definitions to your shell by executing source marco.sh.


Solution

Bash
1
2
3
4
5
6
7
8
#!/bin/bash
marco() {
        echo "$(pwd)" > $HOME/marco_history.log
        echo "save pwd - $(pwd)"
}
polo() {
        cd "$(cat "$HOME/marco_history.log")"
}

Problem3

Say you have a command that fails rarely. In order to debug it you need to capture its output but it can be time consuming to get a failure run. Write a bash script that runs the following script until it fails and captures its standard output and error streams to files and prints everything at the end. Bonus points if you can also report how many runs it took for the script to fail.

Bash
 #!/usr/bin/env bash

 n=$(( RANDOM % 100 ))

 if [[ n -eq 42 ]]; then
    echo "Something went wrong"
    >&2 echo "The error was using magic numbers"
    exit 1
 fi

 echo "Everything went according to plan"


Solution

Bash
#!/usr/bin/env bash
count=0
echo > out.log

while true
do
        ./task3.sh &>> out.log
        if [[ $? -ne 0 ]]; then
                cat out.log
                echo "failed after $count times"
                break
        fi
        ((count++))
done

Problem4

As we covered in the lecture find’s -exec can be very powerful for performing operations over the files we are searching for. However, what if we want to do something with all the files, like creating a zip file? As you have seen so far commands will take input from both arguments and STDIN. When piping commands, we are connecting STDOUT to STDIN, but some commands like tar take inputs from arguments. To bridge this disconnect there’s the xargs command which will execute a command using STDIN as arguments. For example ls | xargs rm will delete the files in the current directory.

Your task is to write a command that recursively finds all HTML files in the folder and makes a zip with them. Note that your command should work even if the files have spaces (hint: check -d flag for xargs).


Solution

prerequisite

Bash
1
2
3
4
mkdir html_root
cd html_root
mkdir html_{a..h}
touch html_{a..h}/{a..h}.html

code

Bash
find . -type f -name '*.html' | xargs -d '\n' tar -cvzf html.zip
  1. find . -type f -name "*.html"

    • find searches for files and directories.
    • . specifies the current directory as the starting point.
    • -type f filters the results to include only files (not directories or other types).
    • -name "*.html" matches files with the .html extension.
    • Output: A list of .html file paths.
  2. | (Pipe symbol)

    • Passes the output of find (list of .html files) as input to the next command, xargs.
  3. xargs -d '\n'

    • xargs converts the input (list of file paths) into arguments for the tar command.
    • -d '\n' specifies that each line of input is treated as a separate file. This ensures paths with spaces or special characters are handled correctly.
  4. tar -cvzf html.zip

    • tar creates compressed archives.
    • -c : Creates a new archive.
    • -v : Displays a list of files being added to the archive (verbose mode).
    • -z : Compresses the archive using gzip.
    • -f html.zip: Names the output file html.zip.

Problem5

(Advanced) Write a command or script to recursively find the most recently modified file in a directory. More generally, can you list all files by recency?


Solution

Bash
find . -type f -print0 | xargs -0 ls -lt | head -1

Summary

uh... I find that I'm still quite uncomfortable with the grammar of bash script. The next step I think, before next lecture, maybe is to pursue a deeper and better understanding of bash script.

The Shell

约 1290 个字 103 行代码 预计阅读时间 6 分钟

前言

临近期末,前方有一大堆的背诵+考试等着我,但我好像不是很慌(这是什么原因...), 感觉我是那种不死到临头不会动的人,闲的无聊决定刷一下这门广受好评的公开课,就当做娱乐放松罢(计划使用英文笔记,锻炼一下写作能力,如果有大量错误请见谅 ----课程链接: 2020 Lectures · Missing Semester

本章节链接: Missing Semester Overview

1. What is the shell

All kinds of shell share one common core: they allow you to run programs, give them input, and inspect their output in a semi-structured way

As I use macos, my shell is terminal or namely zsh. (uh... btw, to be honest, I don't quiet know the differences between zsh and terminal and shell, hope I'm right..

2. Using the shell

When you launch your terminal, you will see a prompt that often looks a little like this:

Bash
missing:~$ 

Down below is what my shell looks like:

Bash
cangjingyue@chenjinyangdeMacBook-Air ~ % 

This is the main textual interface to the shell. It tells you that you are on the machine missing and that your “current working directory”, or where you currently are, is ~ (short for “home”). The $ tells you that you are not the root user (more on that later). At this prompt you can type a command, which will then be interpreted by the shell.


Some basic command

  1. date

date prints the current date and time

Bash
1
2
3
missing:~$ date
Fri 10 Jan 2020 11:49:31 AM EST
missing:~$ 


  1. echo

echo simply prints out its arguments.

Bash
missing:~$ echo hello
hello

If you want to provide an argument that contains spaces or other special characters (e.g., a directory named “My Photos”), you can either quote the argument with ' or " ("My Photos"), or escape just the relevant characters with \ (My\ Photos).

But... it seems that my macos doesn't need those quote or back slash, it will also print out the correct arguments

just like ⬇️

Bash
cangjingyue@chenjinyangdeMacBook-Air ~ % echo My Photos
My Photos

If the shell is asked to execute a command that doesn’t match one of its programming keywords, it consults an environment variable called $PATH that lists which directories the shell should search for programs when it is given a command:

Bash
1
2
3
4
5
6
missing:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
missing:~$ which echo
/bin/echo
missing:~$ /bin/echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

3. Navigating in the shell

1. pwd

pwd prints the absolute path of current working directory

Bash
missing:~$ pwd
/home/missing

2. cd

cd change your working directory to another

Bash
missing:~$ cd /home
missing:/home$ pwd
/home
missing:/home$ cd ..
missing:/$ pwd
/
missing:/$ cd ./home
missing:/home$ pwd
/home
missing:/home$ cd missing
missing:~$ pwd
/home/missing

3. ls

To see what lives in a given directory, we use the ls command:

Bash
missing:~$ ls
missing:~$ cd ..
missing:/home$ ls
missing
missing:/home$ cd ..
missing:/$ ls
bin
boot
dev
etc
home
...

4. ls -l

ls -l use a long listing format:

Bash
missing:~$ ls -l /home
drwxr-xr-x 1 missing  users  4096 Jun 15  2019 missing
  • First, the d at the beginning of the line tells us that missing is a directory.
  • Then follow three groups of three characters (rwx). These indicate what permissions the owner of the file (missing), the owning group (users), and everyone else respectively have on the relevant item.
  • A - indicates that the given principal does not have the given permission.
  • Above, only the owner is allowed to modify (w) the missing directory (i.e., add/remove files in it).
  • To enter a directory, a user must have “search” (represented by “execute”: x) permissions on that directory (and its parents).
  • To list its contents, a user must have read ® permissions on that directory. For files, the permissions are as you would expect.
  • Quick summary: rwx represents the permissions, r for read, w for modify, x for execute, - for don't have the given permission.
  • Notice that nearly all the files in /bin have the x permission set for the last group, “everyone else”, so that anyone can execute those programs.

5. other else

  • mv (to rename/move a file)
  • cp (to copy a file)
  • mkdir (to make a new directory).
  • man ( It takes as an argument the name of a program, and shows you its manual page. Press q to exit.)
Bash
missing:~$ man ls

4. Connecting programs

< file and > file. These let you rewire the input and output streams of a program to a file respectively:

Bash
1
2
3
4
5
6
7
8
missing:~$ echo hello > hello.txt
missing:~$ cat hello.txt
hello
missing:~$ cat < hello.txt
hello
missing:~$ cat < hello.txt > hello2.txt
missing:~$ cat hello2.txt
hello

cat is a program that concatenates files. When given file names as arguments, it prints the contents of each of the files in sequence to its output stream. But when cat is not given any arguments, it prints contents from its input stream to its output stream (like in the third example above).

You can also use >> to append to a file

The | operator lets you “chain” programs such that the output of one is the input of another:

Bash
1
2
3
4
missing:~$ ls -l / | tail -n1
drwxr-xr-x 1 root  root  4096 Jun 20  2019 var
missing:~$ curl --head --silent google.com | grep --ignore-case content-length | cut --delimiter=' ' -f2
219

The command tail means print the tail lines of a file, -n means the index of the line(starting from 1 in the end), -n1 means to print the exactly last one line of the file.

5. A versatile and powerful tool

On most Unix-like systems, one user is special: the “root” user.

The root user is above (almost) all access restrictions, and can create, read, update, and delete any file in the system.

using the sudo command. As its name implies, it lets you “do” something “as su” (short for “super user”, or “root”)

One thing you need to be root in order to do is writing to the sysfs file system mounted under /sys. sysfs exposes a number of kernel parameters as files, so that you can easily reconfigure the kernel on the fly without specialized tools. Note that sysfs does not exist on Windows or macOS.

For example, the brightness of your laptop’s screen is exposed through a file called brightness under

Bash
/sys/class/backlight

By writing a value into that file, we can change the screen brightness. Your first instinct might be to do something like:

Bash
1
2
3
4
5
6
$ sudo find -L /sys/class/backlight -maxdepth 2 -name '*brightness*'
/sys/class/backlight/thinkpad_screen/brightness
$ cd /sys/class/backlight/thinkpad_screen
$ sudo echo 3 > brightness
An error occurred while redirecting file 'brightness'
open: Permission denied

Simply speaking, the error occurs because the shell (which is authenticated just as your user) tries to open the brightness file for writing, before setting that as sudo echo’s output, but is prevented from doing so since the shell does not run as root.

Bash
$ echo 3 | sudo tee brightness

Since the tee program is the one to open the /sys file for writing, and it is running as root, the permissions all work out.

6. exercises

Warning

I will write my own solution to the problem, If you want to solve those by yourself, please stop.


1. Create a new directory called missing under /tmp.

Bash
cd /tmp
mkdir missing

2. Use touch to create a new file called semester in missing.

Bash
cd missing
touch semster

5. Write the following into that file, one line at a time:

Bash
#!/bin/sh
curl --head --silent https://missing.csail.mit.edu

The first line might be tricky to get working. It’s helpful to know that # starts a comment in Bash, and ! has a special meaning even within double-quoted (") strings. Bash treats single-quoted strings (') differently: they will do the trick in this case. See the Bash Quoting (Bash Reference Manual) manual page for more information.

Bash
echo '#!/bin/sh' > semster
echo 'curl --head --silent https://missing.csail.mit.edu' >> semster

6. execute the file

result:

Bash
cangjingyue@chenjinyangdeMacBook-Air missing % ./semster
zsh: permission denied: ./semester

doesn't work...

using ls -l to check the permissions:

Bash
1
2
3
cangjingyue@chenjinyangdeMacBook-Air missing % ls -l 
total 8
-rw-r--r--  1 cangjingyue  wheel  61 Dec  1 10:44 semster

7. Use chmod to make it possible to run the command ./semester

First, quick dive to chmod

chmod : change file modes or Access Control Lists

Usage: chmod [选项]... 模式[,模式]... 文件...

mode define the permissions of the file or directory, usually 3 number. Each number represents the permissions of user, group and other respectively.

Each write, read, and execute permissions have the following number value:

  • r (read) = 4
  • w (write) = 2
  • x (execute) = 1
  • no permissions = 0

To find out the file’s permissions in numeric mode simply calculate the totals for all users classes. For example, to give read, write and execute permission to the file’s owner, read and execute permissions to the file’s group and only read permissions to all other users you would do the following:

  • Owner: rwx=4+2+1=7
  • Group: r-x=4+0+1=5
  • Others: r-x=4+0+0=4

Using the above knowledge, the solution:

Bash
chmod 777 semester
./semster

result

Bash
HTTP/2 200 
server: GitHub.com
content-type: text/html; charset=utf-8
last-modified: Thu, 24 Oct 2024 16:34:06 GMT
access-control-allow-origin: *
etag: "671a76fe-205d"
expires: Sat, 30 Nov 2024 08:50:54 GMT
cache-control: max-age=600
x-proxy-cache: MISS
x-github-request-id: FFD8:1DA486:102D65D:10CDF1E:674ACF96
accept-ranges: bytes
age: 0
date: Sun, 01 Dec 2024 03:08:59 GMT
via: 1.1 varnish
x-served-by: cache-itm1220038-ITM
x-cache: HIT
x-cache-hits: 0
x-timer: S1733022540.782095,VS0,VE181
vary: Accept-Encoding
x-fastly-request-id: f004543c54e1f7139ecb9f84682d652aa24b9a04
content-length: 8285

8. Use | and > to write the “last modified” date output by semester into a file called last-modified.txt in your home directory.

Bash
./semster | grep last-modified > ~/last-modified.txt

result

Bash
cangjingyue@chenjinyangdeMacBook-Air missing % cat ~/last-modified.txt 
last-modified: Thu, 24 Oct 2024 16:34:06 GMT

git commit 规范

约 679 个字 33 行代码 预计阅读时间 3 分钟

Commit message 格式

每次提交,Commit message 都包括三个部分:headerbodyfooter

git commit
1
2
3
4
5
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

其中,header 是必需的,body 和 footer 可以省略。 不管是哪一个部分,任何一行都不得超过72个字符(或100个字符)。这是为了避免自动换行影响美观。

Header部分只有一行,包括三个字段:type(必需)、scope(可选)和subject(必需)。


1. type

用于说明 commit 的类别,只允许使用下面7个标识。

  • feat:新功能(feature)
  • fix:修补bug
  • docs:文档(documentation)
  • style: 格式(不影响代码运行的变动)
  • refactor:重构(即不是新增功能,也不是修改bug的代码变动)
  • test:增加测试
  • chore:构建过程或辅助工具的变动

如果type为featfix,则该 commit 将肯定出现在 Change log 之中。其他情况(docschorestylerefactortest)由你决定,要不要放入 Change log,建议是不要。


2. scope

scope用于说明 commit 影响的范围,比如数据层、控制层、视图层等等,视项目不同而不同。

例如在Angular,可以是 $location, $browser, $compile, $rootScope, ngHref, ngClick, ngView等。

如果你的修改影响了不止一个scope,你可以使用 * 代替。


3. subject

subject是 commit 目的的简短描述,不超过50个字符。

其他注意事项:

  • 动词开头,使用第一人称现在时,比如change,而不是changed或changes
  • 第一个字母小写
  • 结尾不加句号(.)

Body

Body 部分是对本次 commit 的详细描述,可以分成多行。下面是一个范例。

git commit
1
2
3
4
5
6
7
More detailed explanatory text, if necessary.  Wrap it to 
about 72 characters or so. 

Further paragraphs come after blank lines.

- Bullet points are okay, too
- Use a hanging indent

有3个注意点:

  • 使用第一人称现在时,比如使用change而不是changed或changes。
  • 永远别忘了第2行是空行
  • 应该说明代码变动的动机,以及与以前行为的对比

Footer 部分只用于以下两种情况:

  1. 不兼容变动

如果当前代码与上一个版本不兼容,则 Footer 部分以BREAKING CHANGE开头,后面是对变动的描述、以及变动理由和迁移方法。

git commit
BREAKING CHANGE: isolate scope bindings definition has changed.

    To migrate the code follow the example below:

    Before:

    scope: {
      myAttr: 'attribute',
    }

    After:

    scope: {
      myAttr: '@',
    }

    The removed `inject` wasn't generaly useful for directives so there should be no code using it.
  1. 关闭issue

如果当前 commit 针对某个issue,那么可以在 Footer 部分关闭这个 issue 。

git commit
Closes #234

Revert

还有一种特殊情况,如果当前 commit 用于撤销以前的 commit,则必须以revert:开头,后面跟着被撤销 Commit 的 Header。

git commit
1
2
3
revert: feat(pencil): add 'graphiteWidth' option

This reverts commit 667ecc1654a317a13331b17617d973392f415f02.

Body部分的格式是固定的,必须写成This reverts commit &lt;hash>.,其中的hash是被撤销 commit 的 SHA 标识符。

如果当前 commit 与被撤销的 commit,在同一个发布(release)里面,那么它们都不会出现在 Change log 里面。如果两者在不同的发布,那么当前 commit,会出现在 Change log 的Reverts小标题下面。

参考资料

git commit 规范指南

Dijkstra算法

约 94 个字 64 行代码 1 张图片 预计阅读时间 1 分钟

Pasted image 20240717033502

技巧: 如果图为无向图(有向图反转)且不存在负边,要求求各个点 i 到同一终点 n 的最短路径,可以设置Dijkstra的起点和终点 均为 n,求解得到的 distance 数组即 各个点 i 到终点 n 的最短距离。

743. 网络延迟时间为例

Dijkstra算法模版

Java 题解:

Java
class Solution {
    public int networkDelayTime(int[][] times, int n, int s) {
        ArrayList<ArrayList<int[]>> graph = new ArrayList<>();
        // 节点下标为 1 - n
        for (int i = 0; i <= n; i++) {
            graph.add(new ArrayList<>());
        }
        for (int[] edge : times) {
            graph.get(edge[0]).add(new int[]{edge[1], edge[2]});
        }
        int[] distance = new int[n + 1];
        Arrays.fill(distance, Integer.MAX_VALUE);
        distance[s] = 0;
        boolean[] visted = new boolean[n + 1];
        PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> a[1] - b[1]);
        heap.add(new int[]{s, 0});
        while (!heap.isEmpty()) {
            int u = heap.poll()[0];
            if (visted[u]) {
                continue;
            }
            visted[u] = true;
            for (int[] edge : graph.get(u)) {
                int v = edge[0];
                int w = edge[1];
                if (!visted[v] && distance[u] + w < distance[v]) {
                    distance[v] = distance[u] + w;
                    heap.add(new int[] {v, distance[u] + w});
                }
            }
        }
        int ans = Integer.MIN_VALUE;
        for (int i = 1; i <= n; i++) {
            if (distance[i] == Integer.MAX_VALUE) {
                return -1;
            }
            ans = Math.max(ans, distance[i]);
        }
        return ans;
    }
}

Python 题解:

Python
class Solution:
    def networkDelayTime(self, times: List[List[int]], n: int, s: int) -> int:
        graph = [[] for _ in range(n + 1)]
        for u, v, w in times:
            graph[u].append((v, w))

        distance = [inf for _ in range(n + 1)]
        distance[s] = 0
        visited = [False for _ in range(n + 1)]

        heap = [(0, s)]
        while heap:
            _, u = heappop(heap)
            if visited[u]:
                continue
            visited[u] = True
            for v, w in graph[u]:
                if not visited[v] and distance[u] + w < distance[v]:
                    distance[v] = distance[u] + w
                    heappush(heap, (distance[v], v))
        ans = max(distance[1:])
        return ans if ans < inf else -1

滑动窗口模版

约 21 个字 36 行代码 预计阅读时间 1 分钟

统一模版

Java
1
2
3
4
5
6
7
8
//外层循环扩展右边界,内层循环扩展左边界
for (int l = 0, r = 0 ; r < n ; r++) {
        //当前考虑的元素
        while (l <= r && check()) {//区间[left,right]不符合题意
        //扩展左边界
    }
    //区间[left,right]符合题意,统计相关信息
}



模版题:3. 无重复字符的子串

Java题解

Java
class Solution {
    public int lengthOfLongestSubstring(String s) {
        char[] ss = s.toCharArray();
        Set<Character> set = new HashSet<>();
        int res = 0;
        for (int left = 0, right = 0; right < s.length(); right++) {
            char ch = ss[right];
            while (set.contains(ch)) {
                set.remove(ss[left]);
                left++;
            }
            set.add(ss[right]);
            res = Math.max(res, right - left + 1);
        }
        return res;
    }
}

python题解

Python
class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        x = set()
        res, left = 0, 0
        for right in range(len(s)):
            while s[right] in x:
                x.remove(s[left])
                left += 1
            x.add(s[right])
            res = max(res, right - left + 1)
        return res