#925 转载:如何表达

2023-11-08

很多时候,我们表达的目的,不是为了让自己说的开心,而是让听的人清楚。
那如何表达,才能让对方更容易理解,更快抓住重点。
今天我们我们重发一篇文章《如何清晰表达观点?》来聊聊表达这件事,希望对你有所启发。
以下是这篇重发正文。

#924 Redis 创始人 antirez 提供的一个 C 语言示例:聊天服务器

2023-11-04

代码地址:https://github.com/antirez/smallchat
视频讲解:https://www.youtube.com/watch?v=eT02gzeLmF0

作者给做前端开发的朋友写的一个网络编程示例,只有 200 多行纯 C 代码,没有第三方依赖,使用了 select 多路复用实现命令行聊天。
可以用来复习一下 C 编程。

简化部分注释后:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/select.h>

/* ============================ 数据结构 ================================= */

#define MAX_CLIENTS 1000 // This is actually the higher file descriptor.
#define SERVER_PORT 7711

/* This structure represents a connected client. There is very little
 * info about it: the socket descriptor and the nick name, if set, otherwise
 * the first byte of the nickname is set to 0 if not set.
 * The client can set its nickname with /nick <nickname> command. */
struct client {
    int fd;     // Client socket.
    char *nick; // Nickname of the client.
};

/* This global structure encasulates the global state of the chat. */
struct chatState {
    int serversock;     // Listening server socket.
    int numclients;     // Number of connected clients right now.
    int maxclient;      // The greatest 'clients' slot populated.
    struct client *clients[MAX_CLIENTS]; // Clients are set in the corresponding
                                         // slot of their socket descriptor.
};

struct chatState *Chat; // Initialized at startup.

/* ======================== 网络方法 ========================== */

/* Create a TCP socket listening to 'port' ready to accept connections. */
int createTCPServer(int port) {
    int s, yes = 1;
    struct sockaddr_in sa;

    if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1;
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); // Best effort.

    memset(&sa,0,sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(port);
    sa.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(s,(struct sockaddr*)&sa,sizeof(sa)) == -1 ||
        listen(s, 511) == -1)
    {
        close(s);
        return -1;
    }
    return s;
}

/* Set the specified socket in non-blocking mode, with no delay flag. */
int socketSetNonBlockNoDelay(int fd) {
    int flags, yes = 1;

    /* Set the socket nonblocking.
     * Note that fcntl(2) for F_GETFL and F_SETFL can't be
     * interrupted by a signal. */
    if ((flags = fcntl(fd, F_GETFL)) == -1) return -1;
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) return -1;

    /* This is best-effort. No need to check for errors. */
    setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes));
    return 0;
}

/* If the listening socket signaled there is a new connection ready to
 * be accepted, we accept(2) it and return -1 on error or the new client
 * socket on success. */
int acceptClient(int server_socket) {
    int s;

    while(1) {
        struct sockaddr_in sa;
        socklen_t slen = sizeof(sa);
        s = accept(server_socket,(struct sockaddr*)&sa,&slen);
        if (s == -1) {
            if (errno == EINTR)
                continue; /* Try again. */
            else
                return -1;
        }
        break;
    }
    return s;
}

/* We also define an allocator that always crashes on out of memory: you
 * will discover that in most programs designed to run for a long time, that
 * are not libraries, trying to recover from out of memory is often futile
 * and at the same time makes the whole program terrible. */
void *chatMalloc(size_t size) {
    void *ptr = malloc(size);
    if (ptr == NULL) {
        perror("Out of memory");
        exit(1);
    }
    return ptr;
}

/* Also aborting realloc(). */
void *chatRealloc(void *ptr, size_t size) {
    ptr = realloc(ptr,size);
    if (ptr == NULL) {
        perror("Out of memory");
        exit(1);
    }
    return ptr;
}

/* ====================== 聊天服务实现 ======================== */

/* Create a new client bound to 'fd'. This is called when a new client
 * connects. As a side effect updates the global Chat state. */
struct client *createClient(int fd) {
    char nick[32]; // Used to create an initial nick for the user.
    int nicklen = snprintf(nick,sizeof(nick),"user:%d",fd);
    struct client *c = chatMalloc(sizeof(*c));
    socketSetNonBlockNoDelay(fd); // Pretend this will not fail.
    c->fd = fd;
    c->nick = chatMalloc(nicklen+1);
    memcpy(c->nick,nick,nicklen);
    assert(Chat->clients[c->fd] == NULL); // This should be available.
    Chat->clients[c->fd] = c;
    /* We need to update the max client set if needed. */
    if (c->fd > Chat->maxclient) Chat->maxclient = c->fd;
    Chat->numclients++;
    return c;
}

/* Free a client, associated resources, and unbind it from the global
 * state in Chat. */
void freeClient(struct client *c) {
    free(c->nick);
    close(c->fd);
    Chat->clients[c->fd] = NULL;
    Chat->numclients--;
    if (Chat->maxclient == c->fd) {
        /* Ooops, this was the max client set. Let's find what is
         * the new highest slot used. */
        int j;
        for (j = Chat->maxclient-1; j >= 0; j--) {
            if (Chat->clients[j] != NULL) Chat->maxclient = j;
            break;
        }
        if (j == -1) Chat->maxclient = -1; // We no longer have clients.
    }
    free(c);
}

/* Allocate and init the global stuff. */
void initChat(void) {
    Chat = chatMalloc(sizeof(*Chat));
    memset(Chat,0,sizeof(*Chat));
    /* No clients at startup, of course. */
    Chat->maxclient = -1;
    Chat->numclients = 0;

    /* Create our listening socket, bound to the given port. This
     * is where our clients will connect. */
    Chat->serversock = createTCPServer(SERVER_PORT);
    if (Chat->serversock == -1) {
        perror("Creating listening socket");
        exit(1);
    }
}

/* Send the specified string to all connected clients but the one
 * having as socket descriptor 'excluded'. If you want to send something
 * to every client just set excluded to an impossible socket: -1. */
void sendMsgToAllClientsBut(int excluded, char *s, size_t len) {
    for (int j = 0; j <= Chat->maxclient; j++) {
        if (Chat->clients[j] == NULL ||
            Chat->clients[j]->fd == excluded) continue;

        /* Important: we don't do ANY BUFFERING. We just use the kernel
         * socket buffers. If the content does not fit, we don't care.
         * This is needed in order to keep this program simple. */
        write(Chat->clients[j]->fd,s,len);
    }
}

/* The main() function implements the main chat logic:
 * 1. Accept new clients connections if any.
 * 2. Check if any client sent us some new message.
 * 3. Send the message to all the other clients. */
int main(void) {
    initChat();

    while(1) {
        fd_set readfds;
        struct timeval tv;
        int retval;

        FD_ZERO(&readfds);
        /* When we want to be notified by select() that there is
         * activity? If the listening socket has pending clients to accept
         * or if any other client wrote anything. */
        FD_SET(Chat->serversock, &readfds);

        for (int j = 0; j <= Chat->maxclient; j++) {
            if (Chat->clients[j]) FD_SET(j, &readfds);
        }

        /* Set a timeout for select(), see later why this may be useful
         * in the future (not now). */
        tv.tv_sec = 1; // 1 sec timeout
        tv.tv_usec = 0;

        /* Select wants as first argument the maximum file descriptor
         * in use plus one. It can be either one of our clients or the
         * server socket itself. */
        int maxfd = Chat->maxclient;
        if (maxfd < Chat->serversock) maxfd = Chat->serversock;
        retval = select(maxfd+1, &readfds, NULL, NULL, &tv);
        if (retval == -1) {
            perror("select() error");
            exit(1);
        } else if (retval) {

            /* If the listening socket is "readable", it actually means
             * there are new clients connections pending to accept. */
            if (FD_ISSET(Chat->serversock, &readfds)) {
                int fd = acceptClient(Chat->serversock);
                struct client *c = createClient(fd);
                /* Send a welcome message. */
                char *welcome_msg =
                    "Welcome to Simple Chat! "
                    "Use /nick <nick> to set your nick.\n";
                write(c->fd,welcome_msg,strlen(welcome_msg));
                printf("Connected client fd=%d\n", fd);
            }

            /* Here for each connected client, check if there are pending
             * data the client sent us. */
            char readbuf[256];
            for (int j = 0; j <= Chat->maxclient; j++) {
                if (Chat->clients[j] == NULL) continue;
                if (FD_ISSET(j, &readfds)) {
                    /* Here we just hope that there is a well formed
                     * message waiting for us. But it is entirely possible
                     * that we read just half a message. In a normal program
                     * that is not designed to be that simple, we should try
                     * to buffer reads until the end-of-the-line is reached. */
                    int nread = read(j,readbuf,sizeof(readbuf)-1);

                    if (nread <= 0) {
                        /* Error or short read means that the socket
                         * was closed. */
                        printf("Disconnected client fd=%d, nick=%s\n",
                            j, Chat->clients[j]->nick);
                        freeClient(Chat->clients[j]);
                    } else {
                        /* The client sent us a message. We need to
                         * relay this message to all the other clients
                         * in the chat. */
                        struct client *c = Chat->clients[j];
                        readbuf[nread] = 0;

                        /* If the user message starts with "/", we
                         * process it as a client command. So far
                         * only the /nick <newnick> command is implemented. */
                        if (readbuf[0] == '/') {
                            /* Remove any trailing newline. */
                            char *p;
                            p = strchr(readbuf,'\r'); if (p) *p = 0;
                            p = strchr(readbuf,'\n'); if (p) *p = 0;
                            /* Check for an argument of the command, after
                             * the space. */
                            char *arg = strchr(readbuf,' ');
                            if (arg) {
                                *arg = 0; /* Terminate command name. */
                                arg++; /* Argument is 1 byte after the space. */
                            }

                            if (!strcmp(readbuf,"/nick") && arg) {
                                free(c->nick);
                                int nicklen = strlen(arg);
                                c->nick = chatMalloc(nicklen+1);
                                memcpy(c->nick,arg,nicklen+1);
                            } else {
                                /* Unsupported command. Send an error. */
                                char *errmsg = "Unsupported command\n";
                                write(c->fd,errmsg,strlen(errmsg));
                            }
                        } else {
                            /* Create a message to send everybody (and show
                             * on the server console) in the form:
                             *   nick> some message. */
                            char msg[256];
                            int msglen = snprintf(msg, sizeof(msg),
                                "%s> %s", c->nick, readbuf);
                            if (msglen >= (int)sizeof(msg))
                                msglen = sizeof(msg)-1;
                            printf("%s",msg);

                            /* Send it to all the other clients. */
                            sendMsgToAllClientsBut(j,msg,msglen);
                        }
                    }
                }
            }
        } else {
            /* Timeout occurred. We don't do anything right now, but in
             * general this section can be used to wakeup periodically
             * even if there is no clients activity. */
        }
    }
    return 0;
}

#923 电动车相关知识

2023-11-03

这里的电动车是指骑行的那种,不是小汽车。
又因为武汉限摩,所以这里的电动车是指电动自行车,而非什么轻便型电动车或者电摩。

新国标

电动自行车安全技术规范》(GB 17761-2018)是由国家市场监督管理总局、中国国家标准化管理委员会于 2018 年 5 月 15 日批准发布的,自 2019 年 4 月 15 日正式实施。该标准代替了 1999 年发布的《电动自行车安全技术规范》(GB 17761-1999),标准全部技术内容均为强制性。

  1. 车速:<= 25 km/h(旧国标为 20
  2. 重量:<= 55 kg(旧国标为 40
  3. 电压:<= 48 Vkeep...
  4. 功率:<= 400 W(旧国标为 240
  5. 支持人力(脚踏)骑行(keep...

比旧国标稍微强了那么一丢丢,依旧令人失望。

我认为就现状而言,禁摩令十足的恶法。为禁摩服务的国标也完全没有必要。

上牌步骤

常见品牌

  • 绿源
  • 爱玛
  • 小牛
  • 小刀
  • 九号
  • 台铃
  • 新日
  • 哈啰
  • 雅迪

价格

以淘宝上爱玛官方电动车直销店排名第一的“爱玛新国标电瓶车可带人小型女士代步电动自行车成人可带人长跑王”为例,现在的价格:

  • 爆款 MVP 战神-源彩灰【防爆真空胎/350W 高速电机/前后舒适减震】 48v12ah 1739.00
  • 爆款 MVP 战神-源彩绿【防爆真空胎/350W 高速电机/前后舒适减震】
  • 爆款 MVP 战神-源彩粉【防爆真空胎/350W 高速电机/前后舒适减震】
  • 爆款 MVP 战神 2-源彩莓红【防爆真空胎/350W 高速电机/前后舒适减震】
  • 2023 新品 AT102-星空白【防爆真空胎/350W 高速电机/前后舒适减震】 48v12ah 1509.00
  • 2023 新品 AT102-哑黑【防爆真空胎/350W 高速电机/前后舒适减震】
  • 乐行版小电驴 2.0-素颜绿【小 U 弯把/透视大灯/STT 自动启停】 48v12ah 1919.00
  • 乐行版小电驴 2.0-源彩粉【小 U 弯把/透视大灯/STT 自动启停】
  • 乐行版小电驴 2.0-奶酪白【小 U 弯把/透视大灯/STT 自动启停】
  • 尖货泡泡-源彩咖【时尚车筐/中置减震/基因高亮后尾灯】 48v12ah 2099.00
  • 尖货泡泡-源彩灰【时尚车筐/中置减震/基因高亮后尾灯】
  • 尖货泡泡-雾桃粉【时尚车筐/中置减震/基因高亮后尾灯】
  • 尖货泡泡-气泡绿【时尚车筐/中置减震/基因高亮后尾灯】
  • 尖货泡泡-谧淡蓝【时尚车筐/中置减震/基因高亮后尾灯】
  • 战将-源彩黄【48V20AH 电池/350W 高速电机/前后舒适减震】 48v20ah 1969.00
  • 战将-源彩咖啡【48V20AH 电池/350W 高速电机/前后舒适减震】
  • 战将-豆蔻绿【48V20AH 电池/350W 高速电机/前后舒适减震】
  • 新品元宇宙-甜美粉【可提锂电池/萌感透镜大灯/NFC 智能刷卡】 48v24ah 3739.00
  • 新品元宇宙-爱尔兰【可提锂电池/萌感透镜大灯/NFC 智能刷卡】
  • 新品元宇宙-茶白【可提锂电池/萌感透镜大灯/NFC 智能刷卡】
  • 新品元宇宙-奶糖绿【可提锂电池/萌感透镜大灯/NFC 智能刷卡】

赠送头盔,雨衣,U 型锁。

电池

  • 铅酸
  • 便宜,安全(不容易着火),循环利用(以旧换新)
  • 使用寿命短(一到两年,充电还有记忆效应),亏电,能量密度低(大块头,死重),怕冷
  • 石墨烯:一种很好的材料,但是现阶段还在实验室里。市面上的石墨烯电池都是炒作。
  • 锂电池
  • 贵很多,质量问题或者不正确的使用方式可能导致电池爆燃
  • 使用寿命长,不怎么亏电,储电量高很多

#922 毁于所爱

2023-11-01

进击的巨人中,凯尼 ∙ 阿克曼死之前,对侄子利威尔说的:“我所见过的人全都一样,要么是酒,要么是女人,要么是神、家族、王、梦想、子女、力量...人如果不沉醉于某些东西,估计都撑不下去的吧!所有人都是某些东西的奴隶...”

对应后面艾伦的命运。艾伦从被圈养的艾族人,变成追求自由的少年。然后不停地被迫作出选择,放弃一些重要的东西。再然后,为了这些牺牲不被白白浪费,只能沿着划好的线路继续走下去。到最后,才明白,自己其实成为了自由的奴隶。

追求自由,变成了自由的奴隶。
这个故事其实是在告诉我们,人们很容易会被自己的热爱所伤害,甚至毁灭。


恰巧,今天看了一个抖音短视频(疯狂 123),非常有意思,感觉可以放在一起说说。

2022:手机!把我的孩子都毁了!
2012:电脑!把我的孩子都毁了!
2008:电视机!把我的孩子都毁了!
2000:网络小说...
1999:流行音乐...
1995:小霸王游戏机...
1985:彩色电视...
民国:看戏剧...
明清:斗蛐蛐...
宋朝:蹴鞠...
唐朝:遛鸟...
秦朝:击剑狩猎...
夏商周:不种地...
公元前 5000 年:不狩猎...
...
元古宙:那个多细胞生物把我的孩子都毁了!

有一个很有名的小品,之前经常在电视上播放,1993 年,赵丽蓉、黄宏、蔡明主演的《追星族》,给我的感觉是,好像在那个时候追星是多么可怕的事情。我读小学的时候,就有同学迷各种我不知道的明星。抄歌词,抄明星的各种信息,贴纸,看杂志,把图片剪下来贴本子上,等等。
后来,有各种电视节目(言情剧、动漫),还有光碟,这是洪水猛兽。
再后来,游戏厅、游戏机是洪水猛兽。
再后来,网吧,电脑游戏,又变成了新的洪水猛兽,似乎沾上就没有前途可言了。

现在,倒回来看,“追星族”、“网瘾”,这些词怎么好像都没有听人提到了?我这一代人被毁了么?好像也没那么可怕嘛!
有点喜欢的东西,有点爱好都是正常的事情。前人说过:人无痴者,无可与之交,其无深情也;人无癖者,无可与之交,因其无深情也。
能毁掉一个人的不是上面所有的这些东西,而是放纵,是没有自制力,是没有更坚定的志向和为此付出努力的觉悟。
光靠躲,是躲不开的,就算现在避开了这个坑,早晚还是要掉到一个更大的坑。

退一步说,公正的去看,这些事物难道就都百分百是坏的么?

更重要的是,等到子女十几岁之后,以我的水平,我是自认完全没有什么能力去“引导”他们做什么了,主要靠他们自己努力了。我能做的就是陪伴,还有真诚的沟通。


我又想起来,从小到大听了不少:
谁谁谁喜欢某事之后,就不爱学习,成绩差,一辈子毁掉了;
谁谁谁什么杂事都不做,就关屋子里做题,最后成功考上什么大学之类的故事。
考上大学,进入某个单位,是我们人生的目标么?
这顶多只能算一个阶段性目标。为这个阶段性目标付出多少是一个比较平衡的点?

其实大多数家长都是只看结果的,A 考得好,B 考的不好,那 B 的付出一定没有 A 多。
思考问题的方式也比较简单化,A 这样做收获了好的结果,那么 B 应该照着这个方法肯定没差。
这样应该不合理吧!

#921 作息安排

2023-10-31

理想中的作息时间:

  • 08:00 ~ 09:30 洗漱-运动-早餐
  • 09:30 ~ 11:30 工作(2H)
  • 11:30 ~ 13:00 午餐-午间休息
  • 13:00 ~ 16:00 工作(3H)
  • 16:00 ~ 17:30 休息
  • 17:30 ~ 18:30 晚餐
  • 18:30 ~ 21:30 工作(3H)
  • 21:30 ~ 23:00 夜宵-洗漱
  • 23:00 ~ 01:00 自由安排
  • 01:00 ~ 08:00 休息

根据实际情况调整:

  • 07:00 ~ 09:30 洗漱-运动-早餐-通勤
  • 09:30 ~ 12:00 上午工作(2.5H)
  • 12:00 ~ 13:00 午餐-午间休息
  • 13:30 ~ 19:00 下午工作(5.5H)
  • 19:00 ~ 22:00 通勤-晚餐-家务-洗漱
  • 22:00 ~ 23:00 自由安排
  • 23:00 ~ 07:00 休息

#920 生活感悟

2023-10-30

有时候真的是秀才遇到兵,有理说不清。

可能有些人就是坚持为自己的利益斗争的思想,一言不合就要掀桌子。
可能有些人就是喜欢信口开河,胡搅蛮缠,歇斯底里。

我总是有一种感觉,就好像我正在吃饭,有人一边往我的碗里面丢沙子,我却只能继续默默吃下去。

#919 PromQL Cheat Sheet

2023-10-29

Selecting series

Select latest sample for series with a given metric name:

node_cpu_seconds_total

Select 5-minute range of samples for series with a given metric name:

node_cpu_seconds_total[5m]

Only series with given label values:

node_cpu_seconds_total{cpu="0",mode="idle"}

Complex label matchers (=: equality, !=: non-equality, =~: regex match, !~: negative regex match):

node_cpu_seconds_total{cpu!="0",mode=~"user|system"}

Select data from one day ago and shift it to the current time:

process_resident_memory_bytes offset 1d

Rates of increase for counters

Per-second rate of increase, averaged over last 5 minutes:

rate(demo_api_request_duration_seconds_count[5m])

Per-second rate of increase, calculated over last two samples in a 1-minute time window:

irate(demo_api_request_duration_seconds_count[1m])

Absolute increase over last hour:

increase(demo_api_request_duration_seconds_count[1h])

Aggregating over multiple series

Sum over all series:

sum(node_filesystem_size_bytes)

Preserve the instance and job label dimensions:

sum by(job, instance) (node_filesystem_size_bytes)

Aggregate away the instance and job label dimensions:

sum without(instance, job) (node_filesystem_size_bytes)

Available aggregation operators: sum()min()max()avg()stddev()stdvar()count()count_values()group()bottomk()topk()quantile()

Math between series

Add all equally-labelled series from both sides:

node_memory_MemFree_bytes + node_memory_Cached_bytes

Add series, matching only on the instance and job labels:

node_memory_MemFree_bytes + on(instance, job) node_memory_Cached_bytes

Add series, ignoring the instance and job labels for matching:

node_memory_MemFree_bytes + ignoring(instance, job) node_memory_Cached_bytes

Explicitly allow many-to-one matching:

rate(demo_cpu_usage_seconds_total[1m]) / on(instance, job) group_left demo_num_cpus

Include the version label from "one" (right) side in the result:

node_filesystem_avail_bytes * on(instance, job) group_left(version) node_exporter_build_info

Available arithmetic operators: +, -, \*, /, %, ^

Filtering series by value

Only keep series with a sample value greater than a given number:

node_filesystem_avail_bytes > 10*1024*1024

Only keep series from the left-hand side whose sample values are larger than their right-hand-side matches:

go_goroutines > go_threads

Instead of filtering, return 0 or 1 for each compared series:

go_goroutines > bool go_threads

Match only on specific labels:

go_goroutines > bool on(job, instance) go_threads

Available comparison operators: ==, !=, >, <, >=, <=

Set operations

Include any label sets that are either on the left or right side:

up{job="prometheus"} or up{job="node"}

Include any label sets that are present both on the left and right side:

node_network_mtu_bytes and (node_network_address_assign_type == 0)

Include any label sets from the left side that are not present in the right side:

node_network_mtu_bytes unless (node_network_address_assign_type == 1)

Match only on specific labels:

node_network_mtu_bytes and on(device) (node_network_address_assign_type == 0)

Quantiles from histograms

90th percentile request latency over last 5 minutes, for every label dimension:

histogram_quantile(0.9, rate(demo_api_request_duration_seconds_bucket[5m]))

...for only the path and method dimensions:

histogram_quantile(
  0.9,
  sum by(le, path, method) (
    rate(demo_api_request_duration_seconds_bucket[5m])
  )
)

Changes in gauges

Per-second derivative using linear regression:

deriv(demo_disk_usage_bytes[1h])

Absolute change in value over last hour:

delta(demo_disk_usage_bytes[1h])

Predict value in 1 hour, based on last 4 hours:

predict_linear(demo_disk_usage_bytes[4h], 3600)

Aggregating over time

Average within each series over a 5-minute period:

avg_over_time(go_goroutines[5m])

Get the maximum for each series over a one-day period:

max_over_time(process_resident_memory_bytes[1d])

Count the number of samples for each series over a 5-minute period:

count_over_time(process_resident_memory_bytes[5m])

See all available xxx_over_time() aggregation functions.

Time

Get the Unix time in seconds at each resolution step:

time()

Get the age of the last successful batch job run:

time() - demo_batch_last_success_timestamp_seconds

Find batch jobs which haven't succeeded in an hour:

time() - demo_batch_last_success_timestamp_seconds > 3600

Dealing with missing data

Create one output series when the input vector is empty:

absent(up{job="some-job"})

Create one output series when the input range vector is empty for 5 minutes:

absent_over_time(up{job="some-job"}[5m])

Manipulating labels

Join the values of two labels with a - separator into a new endpoint label:

label_join(rate(demo_api_request_duration_seconds_count[5m]), "endpoint", " ", "method", "path")

Extract part of a label and store it in a new label:

label_replace(up, "hostname", "$1", "instance", "(.+):(\\d+)")

Subqueries

Calculate the 5-minute-averaged rate over a 1-hour period, at the default subquery resolution (= global rule evaluation interval):

rate(demo_api_request_duration_seconds_count[5m])[1h:]

Calculate the 5-minute-averaged rate over a 1-hour period, at a 15-second subquery resolution:

rate(demo_api_request_duration_seconds_count[5m])[1h:15s]

Using the subquery result to get the maximum rate over a 1-hour period:

max_over_time(
  rate(
    demo_api_request_duration_seconds_count[5m]
  )[1h:]
)

#917 转载:Linux 命令行:获取上一个命令的参数

2023-10-24

readline 快捷键

readline 是 GNU 的库,实现命令行编辑功能,bash、ftp、python、zsh、mysql 等程序的命令行界面都是使用 readline 实现的,具体实现有 ctrl-r(查找历史命令)、ctrl-p(上一历史命令)、ctrl-a(跳到行首)等。

最重要的还有本次我们所需的 alt+.(dot)esc+.meta+. 得到上一命令的最后一个参数。还有更多快捷键可以参考 readline shortcuts

mac 上没有 alt 和 meta 键,所以我一般使用 esc+. 来获取上一条最后的参数。

shell/bash 历史展开 (history expand)

!$ 获取上一命令的最后一个参数。

历史展开由命令、参数、操作符三部分构成,分别表示展开哪一条命令、该命令的哪个参数、对命令的操作。命令展开完成之后才会进入 .bash_history 中,即执行 history 命令不会看到用于历史展开的参数。

本节的所有命令都假设当前 bash 已经有如下的历史:

$ history
1 echo 1 2 3 4 5
2 ls 6 7 8 9 10
3 echo 11 12 13 14 15
4 cat 16 17 18 19 20
5 echo 21 22 23 24 25
  • a. 命令(Event Designators),用 ! 开始一个历史展开。
$ !n                # 表示第n条命令,如!2表示执行ls 6 7 8 9 10
$ !-n               # 表示倒数第n条命令,如!-3表示执行echo 11 12 13 14 15
$ !!                # 表示上一条命令,是!-1的快捷方式
$ !string           # 表示以string开始的最近的一条命令,如!echo表示echo 21 22 23 24 25
$ !?string?         # 表示含有string的最近的一条命令,如!?6?表示cat 16 17 18 19 20
$ ^string1^string2^ # 表示执行上一条命令,并将其中的第一个string1替换为string2,如果string1不存在则替换失败,不会执行命令。
$ !#                # 表示当前命令现在已经输入的部分,如echo 1 2 !#会执行echo 1 2 echo 1 2
  • b. 参数(Word Designators),命令中选取指定的参数,: 用于分割命令部分与参数部分。
$ !!:0              # 表示上一命令的第0个参数,即命令本身,得到的是echo
$ !2:n              # 表示第2个命令的第n个参数,如!2:2得到的是7
$ !!:^              # 表示上一命令第1个参数,可进一步简写为!^,与!!:1同义,得到的是21
$ !!:$              # 表示上一命令的最后一个参数,可进一步简写为!$,得到的是25
$ !!:x-y            # 表示第x到第y个参数,-y意为0-y,如!-2:3-4得到的是18 19
$ !!:*              # 表示上一命令的参数部分,可进一步简写为!*,如!!:*得到的是21 22 23 24 25
$ !!:n*             # 跟!!:n-$同义
$ !!:n-             # 意为!!:n-$-1,从第n个参数到倒数第二个参数,如!-2:3-得到的是18 19

通过 bash 历史展开实现创建并 cd 到目录的方式为:

$ mkdir somewhere/dir && cd !#:1          # 其中!#表示本行所有命令"mkdir somewhere/dir && cd”,:1取第一个参数就是目录名
  • ​c. 操作符(Modifiers),在可选的参数部分之后,用一个或多个 : 操作符加特定字符。

  • h 去除最后的一个文件路径部分,

    • 假设上一条命令 echo /tmp/123/456/,则 cd !:1:h:h 意为 cd /tmp/123
    • 假设上一条命令 echo /tmp/123/456,则 cd !:1:h:h 意为 cd /tmp
  • t 去除所有的开始的文件路径部分,
    • 假设上一条命令为 echo /tmp/123/456/,则 cd !:1:t 意为 cd
    • 假设上一条命令为 echo /tmp/123/456,则 cd !:1:t 意为 cd 456
  • r 去除后缀,
    • 假设上一条命令为 echo /tmp/bbs.c,则 echo !:1:r 意为 echo /tmp/bbs
  • e 得到后缀,
    • 假设上一条命令为 echo /tmp/bbs.c,则 echo !:1:e 意为 echo .c
  • p print 命令而不执行
  • [g]s/string1/sting2/ 将命令的 string1 替换为 string2,g 意为全局替换,
    • 假设上一条命令为 echo 1 2 1,则 !:gs/1/3/ 意为 echo 3 2 3
    • 上面的 ^string1^string2^ 相当于 !!:s/string1/string2/
  • [g]& 意为执行上次替换,g 意为全局替换。
    • 接上例,假设上一条命令为 echo 123451,则 !:& 意为 echo 323451

使用符号 $$_

在 shell/bash 里 $ 符号表示当前是普通用户(# 是 root),在 sh 脚本或命令行里,$ 开头的表示变量。

# root 用户
[root@localhost iotl] # echo root
# 普通用户
[root@localhost iotl] $ echo hello

以下是一些特殊变量:

  • $_ 代表上一个命令的最后一个参数
  • $# 参数个数。
  • $0 当前 shell 名称(zsh or bash)或脚本的名字。
  • $1 传递给该 shell 脚本的第一个参数。
  • $2 传递给该 shell 脚本的第二个参数。
  • $@ 表示所有的独立的参数,将各个参数分别加双引号返回。
  • $* 以一对双引号给出参数列表。
  • $$ 脚本运行的当前进程 ID 号或所在命令的 PID。
  • $? 显示最后命令的退出状态,0 表示执行成功,其他表示失败。
  • $! 代表最后执行的后台命令的 PID

总结

  1. 快捷键使用 esc + .
  2. 执行最近的以 xxx 开头的命令 !xxx
  3. 快捷修改上一条命令用三个 ^^^ (例如 ^/^/etc^ls / 改为 ls /etc)
  4. 重复当前行前面的命令 !#
  5. 灵活的找到前面的参数: 冒号 !!:3^ 开始、& 结束(可以缩写为 !^!&,注意不是 !#
  6. 重复前面的命令的参数 !#:1
  7. 实际中更多使用快捷键,比如交换前后字母有 ctrl + tesc + t ,比如交换前后单词(word)有 alt + t (别和 alt + dot 搞混了)

参考链接:

  • [1] http://www.gnu.org/software/bash/manual/bashref.html#History-Interaction
  • [2] http://stackoverflow.com/questions/4009412/bash-first-argument-of-the-previous-command
  • [3] https://www.cnblogs.com/tianyapiaozi/archive/2012/09/21/bash_history_arguments.html