2022/01/24

Special Variable In Linux

Linux 有許多特殊變數可以使用,要用的時候常常不記得,用這篇統整記錄一下,以後用到其他的會持續更新。

name description
-G 存在﹐並且由 GID 所執行的行程所擁有。
-L 存在﹐並且是 symbolic link 。
-O 存在﹐並且由 UID 所執行的行程所擁有。
-S 存在﹐並且是一個 socke 。
-b 存在﹐並且是 block 檔案﹐例如磁碟等。
-c 存在﹐並且是 character 檔案﹐例如終端或磁帶機。
-d 存在﹐並且是一個目錄。
-e 存在。
-f 存在﹐並且是一個檔案。
-g 存在﹐並且有 SGID 屬性。
-k 存在﹐並且有 sticky bit 屬性。
-p 存在﹐並且是用於行程間傳送資訊的 name pipe 或是 FIFO。
-r 存在﹐並且是可讀的。
-s 存在﹐並且體積大於 0 (非空檔)。
-u 存在﹐並且有 SUID 屬性。
-w 存在﹐並且可寫入。
-x 存在﹐並且可執行。
-z 空字串
-n 非空字串

整數比較

name description example
-eq 等於 [ integer1 -eq integer2 ]
-ne 不等於 [ integer1 -ne integer2 ]
-lt 小於 [ integer1 -lt integer2 ]
-gt 大於 [ integer1 -gt integer2 ]
-le 小於或等於 [ integer1 -le integer2 ]
-ge 大於或等於 [ integer1 -ge integer2 ]

預設變數

name description
$1~$9 函數的第一個到第9個的參數
$0 函數所在的腳本名
$# 函數的參數總數
$* 函數的全部參數
$? 顯示最後命令的退出狀態,0 表示沒有錯誤,其他表示有錯誤
$$ 腳本運行的當前 process id
test.sh
#!/bin/bash

echo $0
echo $1
echo $2
echo $#
echo $*
echo $$
bash test.sh a b
test.sh
a
b
2
a b
54877

$? 這個預設變數非常實用,可以用來做許多沒有給足判斷的工具當作一種判斷依據,以這篇 tmux 的範例為例,tmux 的 has-session 並非回傳 boolean,而是 session 存在則無反應,session 不存在時噴出 can’t find session 訊息,因此你無法下這種語法:

if tmux has-session -t "$name"; then
    # do something
fi

取而代之的方法就如同我範例那樣抓最後一個 process 的 exit code 當作判斷依據。

$$ 則是如果該 bash 為 daemon 時,可以將 pid 存放在某個檔案內,這樣就可以偵測如果該 pid 有檔案時先 kill 該 process 之後在重啟,否則同一時間就會同時有兩個 daemon 在執行。

2021/12/08

Imitate Jetbrains Moving Radial Gradient Effect

今天在逛 Jetbrains 新產品 fleet 的網頁時,看到一個圓形漸層會跟著滑鼠移動的特效,覺得很有意思。

手癢來刻一個類似的。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Glow Cursor</title>
    <style>
        .box {
            width: 300px;
            height: 300px;
            border: 2px solid blue;
        }
    </style>
</head>
<body>
<div class="box"></div>
<script>
    var box = document.querySelector('.box');

    box.addEventListener('mousemove', function (e) {
        var rect = e.target.getBoundingClientRect();
        var x = e.clientX - rect.left;
        var y = e.clientY - rect.top;

        box.style.background = `radial-gradient(circle at ${x}px ${y}px, rgba(138, 189, 61, 1) 0%, rgba(255, 255, 255, 1) 150px`;
    });

    box.addEventListener('mouseleave', function () {
        box.style.background = null;
    });
</script>
</body>
</html>

效果大概是這樣 Demo

2021/12/03

Imitate Laravel Collection Sum Funtion in PHP, Python, Node.js

Laravel 包含了許多好用的套件,Collection 應該是最好用的,將陣列導進去以後可以做各種花式操作,自己嘗試在 PHP、Python、Node.js 上模擬一下作法。

PHP

<?php

class Collection
{
    private array $collections;

    public function __construct(array $collections)
    {
        $this->collections = $collections;
    }

    public function sum(Closure $callable = null): int
    {
        $callbackIn = ($callable === null) ? $this->isNotCallback() : $this->isCallback($callable);

        return array_reduce($this->collections, fn($carry, $item) => $carry + $callbackIn($item), 0);
    }

    private function isNotCallback(): Closure
    {
        return fn(int $value): int => $value;
    }

    private function isCallback(Closure $callable): Closure
    {
        return fn(int $value): int => $callable($value);
    }
}

$collection = new Collection([1, 2, 3]);
$cal = fn($item): int => $item + 5;

echo $collection->sum(), PHP_EOL;
echo $collection->sum($cal);

Python

from functools import reduce
from typing import Callable


class Collection(object):
    collections: list

    def __init__(self, collections: list):
        self.collections = collections

    def sum(self, callback=None) -> int:
        callback_in = self.is_not_callable() if callback is None else self.is_callable(callback)

        return reduce(lambda carry, item: carry + callback_in(item), self.collections, 0)

    @staticmethod
    def is_not_callable() -> Callable[[int], int]:
        return lambda x: x

    @staticmethod
    def is_callable(callback) -> Callable[[int], Callable[[int], int]]:
        return lambda x: callback(x)


def call_me() -> Callable[[int], any]:
    return lambda x: x + 5


collection = Collection([1, 2, 3])
print(collection.sum())
print(collection.sum(call_me()))

Node.js

class Collection {
    private collections: any[];

    constructor(collections: any[]) {
        this.collections = collections;
    }

    public sum(callback: Function = null) {
        const callbackIn = (callback === null) ? this.isNotCallable() : this.isCallable(callback);

        return this.collections.reduce((carry, item) => carry + callbackIn(item), 0);
    }

    private isNotCallable(): Function {
        return (value: any) => value;
    }

    private isCallable(callback): Function {
        return (value: any) => callback(value);
    }
}

const collections = new Collection([1, 2, 3]);
const cal = (value) => value + 5;

console.log(collections.sum());
console.log(collections.sum(cal));

2021/12/02

Laravel Connet To MSSQL Server On CentOS

PHP 安裝

# Install PHP 7.4
sudo yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
sudo yum -y install https://rpms.remirepo.net/enterprise/remi-release-7.rpm
sudo yum -y install yum-utils
sudo yum-config-manager --enable remi-php74
sudo yum install -y php php-pdo php-odbc php-mbstring php-zip php-xml

MSSQL Driver 安裝,擇一安裝

1. MSSQL Driver(首選,支援版本 SQL Server 2000 以上)

sudo yum install php-sqlsrv
curl https://packages.microsoft.com/config/rhel/7/prod.repo > /etc/yum.repos.d/mssql-release.repo

sudo yum remove unixODBC-utf16 unixODBC-utf16-devel #to avoid conflicts
sudo ACCEPT_EULA=Y yum install -y msodbcsql17

2. FreeTDS(Sql Server 2000)

sudo yum -y install php-mssql
sudo yum -y install unixODBC-*
sudo yum -y install freetds

cd /usr/lib64
sudo mv libtdsodbc.so.0.0.0 libtdsodbc.so
sudo vi /etc/odbcinst.ini

在最後增加:

[FreeTDS]
Description = ODBC for FreeTDS
Driver64    = /usr/lib64/libtdsodbc.so
Setup64     = /usr/lib64/libtdsodbc.so
FileUsage   = 1
sudo vi /etc/freetds.conf

在最後增加:

[SqlServer2000]
    host = {IP}
    port = 1433
    tds version = 8.0
    client charset = UTF-8

在 Laravel 的配置一切照舊,除了 MSSQL 的 host 要指到 SqlServer2000,若遇到時間格式的問題可以參考此篇,使用 FreeTDS 可以通吃,但怕會有無法預期的問題,不建議同一台 Server 同時使用兩種方式。

參考網站

2021/11/18

update multiple package to latest at once

npm 有一個指令可以偵測套件是否有出新的可以更新,我故意裝了 jquery@2 以及 bootstrap@4。

npm ls
test@1.0.0 D:\node_test
├── bootstrap@4.6.1
└── jquery@2.2.4

我們來執行更新檢查:

npm outdated
Package    Current  Wanted  Latest  Location                Depended by
bootstrap    4.6.1   4.6.1   5.1.3  node_modules/bootstrap  node_test
jquery       2.2.4   2.2.4   3.6.0  node_modules/jquery     node_test

bootstrap 跟 jquery 都有新版本可以更新,更新指令如下:

npm install jquery@latest bootstrap@latest

如果之前就有加入 package.json 的話則不用指定 --save 或是 --save-dev,一兩個套件可能還好,假設今天有十幾個要一次更新就累了,而 npm 本身沒有提供全部更新到最新版的指令,npm update 跑的是在 package.json 的板號下的最新版,所以跨了版號就不執行了。

npmjs 當然有套件可以輔助這件事,像是 npm-check,但我們也可以透過 shell script 自己處理,以下示範 awk 以及 cut 的用法。

# 指令一,利用 awk
npm outdated | sed 1d | awk -F ' ' '{print sprintf("%s@latest", $1)}' | xargs npm install
npm outdated | sed 1d | awk -F ' ' '{print sprintf("%s@%s", $1, $4)}' | xargs npm install

# 指令二,利用 cut
npm outdated | sed 1d | cut -d' ' -f1 | xargs -I {} echo '{}@latest' | xargs npm install

2021/08/20

Form Validator In Angular

Angular 的 ReactiveFormsModule 相當強大,包含了 two way binding data 以及好用的 validator,以下的範例示範怎麼針對單一欄位以及整體表單自製 validator,如果 username 填寫 chan 的話表示帳號重複,年齡大於 100 的話則錯誤,group 的部分兩者都要填寫。

ts
import { Component, OnInit } from '@angular/core';
import { AbstractControl, FormBuilder, ValidationErrors, ValidatorFn } from '@angular/forms';

@Component({
  selector: 'app-form-validator',
  templateUrl: './form-validator.component.html',
  styleUrls: ['./form-validator.component.scss']
})
export class FormValidatorComponent implements OnInit {
  public form = this.fb.group({
    username: ['', [this.userNameChecker()]],
    age: ['', [this.numberChecker()]]
  }, { validators: this.bothRequired() });
  public ageLimit: number = 100;

  constructor(
    private fb: FormBuilder
  ) { }

  ngOnInit(): void {
  }

  get username(): AbstractControl {
    return this.form.controls.username;
  }

  get age(): AbstractControl {
    return this.form.controls.age;
  }

  private userNameChecker(): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      if (control.value === '') {
        return null;
      }

      if (control.value === 'chan') {
        return { userNameExists: true };
      }

      return null;
    }
  }

  private numberChecker(): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      if (control.value === '') {
        return null;
      }

      if (control.value > this.ageLimit) {
        return { tooBig: true };
      }

      return null;
    }
  }

  private bothRequired(): ValidatorFn {
    return (group: AbstractControl): ValidationErrors | null => {
      return (group.get('username')?.value === '' || group.get('age')?.value === '') ? { bothRequired: true } : null;
    }
  }
}
html
<h3>{{ form.value | json }}</h3>
<div [formGroup]="form">
  <div>
    username: <input type="text" formControlName="username">
    <div *ngIf="username.errors?.userNameExists">username exists</div>
  </div>
  <div>
    age: <input type="number" formControlName="age">
    <div *ngIf="age.errors?.tooBig">age should lower than {{ ageLimit }}</div>
  </div>
  <div>
    <button [disabled]="form.invalid">submit</button>
  </div>
</div>

2021/08/13

Stop The Process By Condition In Ansible

Ansible 的任務沒有下中斷的話,即便有使用 when 偵測,playbook 會繼續往下走,可以利用 fail 這個參數讓條件不符合時直接把 process 中斷,以 stat 偵測目錄是否存在來做範例。

- hosts: localhost

  vars:
    path: /tmp/sync/

  tasks:
    - name: "stat {{ path }}"
      stat:
        path: "{{ path }}"
      register: the_dir

    - name: "shutdown when {{ path }} is not exists"
      fail:
        msg: "{{ path }} not exists"
      when: the_dir.stat.exists == False

    - name: "echo yes when {{ path }} exists"
      debug:
        msg: 'yes'
      when: the_dir.stat.exists

當我們設定的 path 不存在時,會直接中斷,不會走道最後的 "echo yes when {{ path }} exists"