BNU-FZH

fengzhenhua@outlook.com

Shell 中的特殊变量

Shell 预定义了一些特殊变量,它们通常包含一个或两个连字符作为前缀:

  • $0 - 当前脚本的名字。
  • $1, $2, ... - 传递给脚本或函数的位置参数。
  • $# - 传递给脚本或函数的参数个数。
  • $* - 所有参数作为一个字符串。
  • $@ - 参数列表(与 $* 类似,但在引号中有区别)。
  • $? - 上一条命令的退出状态。
  • $$ - 当前 Shell 的进程 ID。
  • $! - 最后一个后台进程的 PID。

awk 内置变量

awk 提供了多个内置变量来访问输入数据和其他特性:

  • $n - 表示当前行的第 n 个字段(列),其中 $1 是第一列,$2 是第二列,依此类推。
  • $0 - 表示整个当前行的内容。
  • NF - 当前行的字段数(列数)。
  • NR - 到目前为止处理的总行数(全局行号)。
  • FNR - 当前文件中的行号(对于多文件输入,每个新文件计数会重新开始)。
  • FILENAME - 当前正在读取的文件名。
  • FS - 字段分隔符,默认为空白字符(空格或制表符)。可以设置为其他值以改变字段划分规则。
  • OFS - 输出字段分隔符,默认为空格。当使用逗号 , 分隔输出字段时,awk 会用这个值替换逗号。
  • ORS - 输出记录分隔符,默认为换行符 \n
  • RS - 输入记录分隔符,默认为换行符,即每行作为一个记录。
  • ARGCARGV - 类似于 Shell 的特殊变量,分别表示命令行参数的数量和数组。

这些内置变量提供了对输入数据结构、格式以及程序运行环境的强大控制能力。

冲突和解决

Shell 和 awk 同时内置了 $n 变量,在 Shell 脚本中使用变量会造成混乱。但是注意到:Shell 中使用双引号括起来的变量 "$n" 表示变量,而使用单引号'$n'不表示变量,但是 awk 在单引号'$n'中表示 awk 变量, 所以在任何情况下都使用单引号将 awk 的命令引起来就可以避免冲突。

参考文章

dig 命令简介

dig(Domain Information Groper)是 Linux 和 Unix 系统中用于查询 DNS(域名系统)的强大工具。它提供了对 DNS 查询的详细控制,并且可以用来执行各种类型的 DNS 查询,包括但不限于 A 记录、MX 记录、NS 记录、CNAME 记录、TXT 记录等。dig 是网络管理员和开发者调试 DNS 问题、验证域名配置以及获取有关互联网资源信息的重要工具。

主要特点

  • 多功能性:支持多种查询类型和选项,能够满足复杂的 DNS 查询需求。
  • 详细的输出:提供详尽的查询结果信息,默认情况下输出格式易于阅读,同时也支持简洁模式(如 +short)。
  • 灵活性:用户可以通过命令行参数自定义查询行为,例如指定 DNS 服务器、设置查询超时时间、递归查询等。
  • 批处理能力:支持从文件批量读取域名进行查询(使用 -f 参数),适合自动化脚本或大规模 DNS 数据收集。

基本用法

1
dig [选项] [域名] [记录类型] 

查询特定DNS服务器

1
dig @8.8.8.8 www.baidu.com

批量查询文件

创建一个包含域名列表的文本文件,并使用-f参数进行批量查询。

1
dig -f domains.txt
1
2
3
www.baidu.com
www.bing.com
www.sina.com

批量查询数组

创建一个包含域名列表的数组,并使用-f参数进行批量查询。

1
2
3
4
5
6
MH=(
www.baidu.com
www.bing.com
www.sina.com
)
printf "%s\n" "${MH[@]}" | dig @119.29.29.29 -f /dev/stdin +noall +answer | awk '{print $5, $1}'

通过dnsmasq 实现了本地 DNS 服务的功能,这大大加速的域名解析速度。但是最近在连接校园网时,发现每次连网配置文件/etc/resolv.conf都会被修改为校园网默认的配置,但是这样就无法设置为自动dnsmasq接管服务。

具体原因

当在eth接口启用DHCP后,本地resolv.conf文件将被修改,resolv.conf文件中的DNS地址将被改为从DHCP获取到的地址。这种从DHCP获得的DNS即是Peer DNS 启用DHCP后即便修改/etc/resolv.conf,不久又恢复成原样.

解决方法

  1. 断开网络
  2. /etc/resolv.conf修改为
    1
    2
    3
    4
    5
    # Generated by syndns
    nameserver 127.0.0.1
    nameserver 119.29.29.29
    nameserver 180.76.76.76
    nameserver 1.2.4.8
  3. 设置文件锁
    1
    sudo chattr +i /etc/resolv.conf
  4. 再次修改/etc/resolv.conf必须先解锁,命令为
    1
    sudo chattr -i /etc/resolv.conf

参考文章

一、问题

Shell下有时需要使用随机数,在此总结产生随机数的方法。计算机产生的的只是“伪随机数”,不会产生绝对的随机数(是一种理想随机数)。伪随机数在大量重现时也并不一定保持唯一,但一个好的伪随机产生算法将可以产生一个非常长的不重复的序列。

二、随机数

1、生成随机数的七种方法

(1)通过内部系统变量($RANDOM)

1
echo $RANDOM

生成0-32767之间的整数随机数,若超过5位可以加个固定10位整数,然后进行求余。

生成400000~500000的随机数
1
2
3
4
5
6
7
8
9
10
#!/bin/bash  
function rand(){
min=$1
max=$(($2-$min+1))
num=$(($RANDOM+1000000000)) #增加一个10位的数再求余
echo $(($num%$max+$min))
}
rnd=$(rand 400000 500000)
echo $rnd
exit 0

(2)使用awk的随机函数

1
awk 'BEGIN{srand();print rand()*1000000}' #可以加上if判断

(3)openssl rand产生随机数

openssl rand 用于产生指定长度个bytes的随机字符。-base64-hex对随机字符串进行base64编码或用hex格式显示。

1
2
openssl rand -base64 8 | md5sum | cut -c1-8 #八位字母和数字的组合,3a61800e
openssl rand -base64 8 | cksum | cut -c1-8 #八位数字,10784736

(4)通过时间获得随机数(date)

1
2
3
date +%s%N #生成19位数字,1287764807051101270
date +%s%N | cut -c6-13 #取八位数字,21793709
date +%s%N | md5sum | head -c 8 #八位字母和数字的组合,87022fda
生成1~50的随机数
1
2
3
4
5
6
7
8
9
10
#!/bin/bash 
function rand(){
min=$1
max=$(($2-$min+1))
num=$(date +%s%N)
echo $(($num%$max+$min))
}
rnd=$(rand 1 50)
echo $rnd
exit 0

(5)通过系统内唯一数据生成随机数(/dev/random及/dev/urandom)

/dev/random存储系统当前运行环境的实时数据,可看作系统某时的唯一值,提供优质随机数。

/dev/urandom是非阻塞随机数产生器,读取时不会产生阻塞,速度更快、安全性较差。

1
2
3
4
5
6
7
8
#32f1e953ac
cat /dev/urandom | head -n 10 | md5sum | head -c 10
#生成全字符的随机字符串,08?WU$ZU
cat /dev/urandom | strings -n 8 | head -n 1
#生成数字加字母的随机字符串,Ql2q9CXS,其中 strings -n设置字符串的字符数,head -n设置输出的行数。
cat /dev/urandom | sed -e 's/[^a-zA-Z0-9]//g' | strings -n 8 | head -n 1
#urandom的数据很多使用cat会比较慢,在此使用head读200行,cksum将读取文件内容生成唯一的表示整型数据,cut以” “分割然后得到分割的第一个字段数据
head-200/dev/urandom| cksum |cut-d" " -f1

(6)读取linux的uuid码

UUID码全称是通用唯一识别码 (Universally Unique Identifier, UUID),UUID格式是:包含32个16进制数字,以“-”连接号分为五段,形式为8-4-4-4-12的32个字符。linux的uuid码也是有内核提供的,在/proc/sys/kernel/random/uuid这个文件内。cat/proc/sys/kernel/random/uuid每次获取到的数据都会不同。

1
2
cat /proc/sys/kernel/random/uuid| cksum | cut -f1 -d" " #获取不同的随机整数,1675034933
cat /proc/sys/kernel/random/uuid| md5sum | cut -c1-8 #数字加字母的随机数,d69a7ebf
使用linux uuid 生成100~500随机数
1
2
3
4
5
6
7
8
9
10
#!/bin/bash 
function rand(){
min=$1
max=$(($2-$min+1))
num=$(cat /proc/sys/kernel/random/uuid | cksum | awk -F ' ' '{print $1}')
echo $(($num%$max+$min))
}
rnd=$(rand 100 500)
echo $rnd
exit 0

(7)从元素池中随机抽取取

1
2
3
pool=(a b c d e f g h i j k l m n o p q r s t 1 2 3 4 5 6 7 8 9 10)
num=${#pool[*]}
result=${pool[$((RANDOM%num))]}

用于生成一段特定长度的有数字和字母组成的字符串,字符串中元素来自自定义的池子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash 
length=8
i=1
seq=(0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)
num_seq=${#seq[@]}

while [ "$i" -le "$length" ]
do
seqrand[$i]=${seq[$((RANDOM%num_seq))]}
let "i=i+1"
done

echo "The random string is:"
for j in ${seqrand[@]}
do
echo -n $j
done
echo

2、随机数应用

  1. 随机数在互联网中应用广泛如计算机仿真模拟、数据加密、网络游戏等,在登录某些论坛或游戏时,系统会产生一个由随机数字和字母组成的图片,用户必须正确输入,这是防止恶意攻击的很好的方法,因比较难破解图片格式的字符。其关键技术就是产生随机数,再使用ASP.NET等工具将这些字符串封装成图片格式以作为验证图片。

  2. 网络游戏中也常利用随机数完成一些功能,比如掷骰子、发扑克牌等。以下是连续掷1000次骰子,然后统计出1~6点的次数:

    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
    #!/bin/bash 
    #RANDOM=$$
    PIPS=6
    MAX=1000
    throw=1

    one=0
    two=0
    three=0
    four=0
    five=0
    six=0
    count()
    {
    case "$1" in
    0) let "one=one+1";;
    1) let "two=two+1";;
    2) let "three=three+1";;
    3) let "four=four+1";;
    4) let "five=five+1";;
    5) let "six=six+1";;
    esac
    }

    while [ "$throw" -le "$MAX" ]
    do
    let "dice=RANDOM % $PIPS"
    count $dice
    let "throw=throw+1"
    done

    echo "The statistics results are as follows:"
    echo "one=$one"
    echo "two=$two"
    echo "three=$three"
    echo "four=$four"
    echo "five=$five"
    echo "six=$six"
    RANDOM产生的随机数基本在平均值左右浮动(即方差较小)。

  3. 批量创建10个系统帐号,密码随机 先看看指定用户密码的脚本:

    1
    2
    3
    4
    5
    6
    7
    #!/bin/bash 
    #批量创建10个系统帐号并设置密码,帐号和密码相同
    for name in `seq -w 10`
    do
    #非交互式的输入密码
    useradd linux$name && echo "linux$name" | passwd --stdin linux$name
    done
    10个用户用户名和密码相同都从linux-01到linux-10,再看看用户密码随机生成的脚本:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #!/bin/bash 
    #批量创建10个系统帐号并设置密码
    rm -f user.log
    for name in `seq -w 10`
    do
    #非交互式的输入随机密码
    password=`echo $RANDOM | md5sum | cut -c1-8`
    #可以使用password=`echo "date $RANDOM" | md5sum | cut -c3-11`
    #也可以使用password=`penssl rand -base64 8 | md5sum | cut -c1-8`
    useradd linux$name && echo password | passwd --stdin linux$name
    echo -e "user=linux$name \t passwd=$password" >> user.log #保存用户名密码以查阅
    done
    对比可以看出,随机生成密码的灵活性和保密性,管理员可以打开user.log文件,记录刚创建的十个用户的信息。

三、总结

  1. Shell产生伪随机数的函数$RANDOM,能方便地产生分布较平均的伪随机数,能满足大部分应用的需求。
  2. 产生随机数的方法还有很多并且可以扩展,扩展思路才能选择最近的方式。
  3. 完全引用自: shell产生随机数七种方法的实现

由于 linuxqq 是用 electron 写的,而且目前还有一些 bug, 当点击关闭按钮后程序仍然在后台运行。但是,想重新登录一下,却提示已经登录,而原来的聊天界面又找不到,所以必须完全关闭,然后再重新启动才行。 解决方法是借助pgrep打印所有相关进程,然后一同kill, 命令为

1
sudo kill $(pgrep qq)

由于工作和学习的需要,可能需要同时在一台电脑上维护多个 github 帐号,这时使用 Github-cli 同时使用多个帐号的 Token 认证后,然后就可以随时切换了。具体方法为:

登录认证帐号

1
gh auth login -p ssh --skip-ssh-key --with-token < your_token_here >

注意: 这条命令选择了 ssh的认证方式,所以需要提前配置好 ssh. 同时按 使用Github-Cli管理仓库 取得 Token.

切换帐号

1
gh auth switch -u < your_name_here >

gh auth 选项详解

gh help auth login
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
Authenticate with a GitHub host.

The default hostname is `github.com`. This can be overridden using the `--hostname`
flag.

The default authentication mode is a web-based browser flow. After completion, an
authentication token will be stored securely in the system credential store.
If a credential store is not found or there is an issue using it gh will fallback
to writing the token to a plain text file. See `gh auth status` for its
stored location.

Alternatively, use `--with-token` to pass in a personal access token (classic) on standard input.
The minimum required scopes for the token are: `repo`, `read:org`, and `gist`.

Fine-grained personal access tokens are not supported.

Alternatively, gh will use the authentication token found in environment variables.
This method is most suitable for "headless" use of gh such as in automation. See
`gh help environment` for more info.

To use gh in GitHub Actions, add `GH_TOKEN: ${{ github.token }}` to `env`.

The git protocol to use for git operations on this host can be set with `--git-protocol`,
or during the interactive prompting. Although login is for a single account on a host, setting
the git protocol will take effect for all users on the host.

Specifying `ssh` for the git protocol will detect existing SSH keys to upload,
prompting to create and upload a new key if one is not found. This can be skipped with
`--skip-ssh-key` flag.

For more information on OAuth scopes, <https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps/>.


USAGE
gh auth login [flags]

FLAGS
-p, --git-protocol string The protocol to use for git operations on this host: {ssh|https}
-h, --hostname string The hostname of the GitHub instance to authenticate with
--insecure-storage Save authentication credentials in plain text instead of credential store
-s, --scopes strings Additional authentication scopes to request
--skip-ssh-key Skip generate/upload SSH key prompt
-w, --web Open a browser to authenticate
--with-token Read token from standard input

INHERITED FLAGS
--help Show help for command

EXAMPLES
# Start interactive setup
$ gh auth login

# Authenticate against github.com by reading the token from a file
$ gh auth login --with-token < mytoken.txt

# Authenticate with specific host
$ gh auth login --hostname enterprise.internal

LEARN MORE
Use `gh <command> <subcommand> --help` for more information about a command.
Read the manual at https://cli.github.com/manual
Learn about exit codes using `gh help exit-codes`

参考文章

在编写脚本时避免不了使用密码,所以为了增强安全性,需要使用加密解密技术。本文记录几种加密解密的方法。

Python 与 Bash Shell 的结合

这个命令会让你输入一个字符串,然后会再输出一串加密了的数字.

1
2
3
4
# 加密代码[照直输入]:
python -c 'print reduce(lambda a,b: a*256+ord(b), raw_input("string: "), 0)'
# 解密代码[数字后+P]:
dc -e 输出的数字P

纯 Bash Shell, 含 VIM 的xxd

用 gtalk@gmail.com 作为明文,加密分两步也可以一步。

1
2
3
4
5
6
7
8
# 加密代码:
echo "gtalk@gmail.com" |xxd -ps -u
# 得到:6774616C6B40676D61696C2E636F6D0A
echo "ibase=16; 6774616C6B40676D61696C2E636F6D0A" |bc
# 得到:137514765985002236391382606438443478282
一步加密代码:
echo "ibase=16; $(echo "gtalk@gmail.com" |xxd -ps -u)" |bc
# 得到:137514765985002236391382606438443478282

Base64 编码

1
2
3
4
5
6
# 加密代码:
echo "gtalk@gmail.com" |base64 -i
# 得到:Z3RhbGtAZ21haWwuY29tCg==
# 解密代码:
echo "Z3RhbGtAZ21haWwuY29tCg==" |base64 -d
# 得到:gtalk@gmail.com

Base64 编码,这个很好很强大,适合写加密脚本

实现使用 base64 加密、解密字符串并赋值给变量。网上绝大多数是使用 echo 管道实现 加密和解密字符串的输出。

1
2
3
4
5
pwd=$( base64 -d <<< MQo= )  # 解码
in=$( base64 <<< "123456") # 加密
# 或者
test=$(printf "%s""123456" | base64) # 加密
test=$(printf "%s" MTIzNDU2| base64 -d) # 解密

参考文章

在 Markdown 中添加目录(Table of Contents, ToC)并不是直接支持的功能,但是你可以手动创建一个简单的目录,使用链接和锚点来指向文档中的各个部分。首先,在你想链接到的标题或段落前设置一个锚点(ID),然后在目录部分创建指向这些锚点的链接。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 目录
- [简介](#简介)
- [安装](#安装)
- [使用方法](#使用方法)

# 简介
这是介绍内容。

## 安装
这是安装说明。

## 使用方法
这是使用方法。

注意:不是所有的Markdown解析器都支持通过标题自动创建锚点链接。有些解析器可能会需要你显式地为每个标题定义一个唯一的ID,像这样:

1
## <a name="安装"></a>安装

当编写复杂的 Shell 脚本时,了解脚本在何处停止执行可以极大地帮助调试和优化。本文将介绍几种在 Shell 脚本中判断脚本停止位置的方法。

目录

添加日志记录

通过在脚本的关键位置添加日志记录,可以帮助跟踪脚本的执行进度。例如:

1
echo "Script has reached step 1" >> script.log 

这行命令会在每次执行到该行时向 script.log 文件追加一条消息。

启用调试模式

你可以使用 -x 选项来启用 bash 的调试模式,这会让它打印出执行的每个命令:

1
bash -x ./your_script.sh

或者在脚本内部临时开启调试:

1
2
3
set -x
# 你的代码
set +x

这样可以在脚本的特定部分开启或关闭调试输出。

错误捕获和通知

使用 trap 命令可以在发生错误时执行特定的命令,比如发送通知或写入日志:

1
trap 'echo "Error at line $LINENO: $BASH_COMMAND"' ERR

这将在任何命令失败时触发,并打印出错误发生的行号和命令。

检查退出状态

每个命令执行后都会返回一个退出状态码,通常0表示成功,非零值表示错误。可以通过检查 $? 变量来决定是否继续执行:

1
2
3
4
5
command
if [ $? -ne 0 ]; then
echo "Command failed"
exit 1
fi

这种方式确保只有当上一条命令成功时才会继续执行。

在 Shell 脚本中避免输入 sudo 密码的方法有几种,但每种方法都有其适用场景和安全考量。以下是几种常见的方法:

1. 修改 /etc/sudoers 文件

你可以编辑 /etc/sudoers 文件(推荐使用 visudo 命令以确保语法正确),为特定用户或用户组添加不需要密码的权限。

例如,要允许用户 username 在不输入密码的情况下运行所有命令,可以添加如下行:

1
username ALL=(ALL) NOPASSWD:ALL 

如果你想限制到特定命令,比如只允许不输入密码地运行 apt-get update,可以这样做:

1
username ALL=(ALL) NOPASSWD:/usr/bin/apt-get update

2. 使用sudo -S和环境变量

虽然这不是最推荐的安全实践,但在某些情况下你可能需要自动化流程,并且能够通过环境变量传递密码。你可以设置一个环境变量来保存密码,并结合 echosudo -S 来实现这一点。但是,请注意这种方法存在安全风险,因为密码可能会被其他进程看到。

1
2
export SUDO_PASSWORD="your_password_here"
echo "$SUDO_PASSWORD" | sudo -S command_to_run

警告: 这种方式并不安全,应该尽量避免使用。

3. 使用sudo-n选项

-n 选项可以让 sudo 尝试非交互式执行命令,即如果当前用户没有足够的权限或缓存的凭证已过期,则不会提示输入密码而是直接失败。这可以用于检查是否可以在不提供密码的情况下执行命令。

1
sudo -n command_to_run

4. 利用sudo的时间戳

sudo 会缓存用户的认证一段时间(默认是15分钟)。如果你在一个脚本中多次调用 sudo,那么在第一次成功输入密码后,在这段时间内后续的 sudo 调用将不再要求密码。

安全建议

无论选择哪种方法,都应该谨慎行事,确保最小权限原则 ( Principle of Least Privilege ) 得以遵守,即给予用户完成工作所需的最少权限。修改系统配置文件如 /etc/sudoers 应该非常小心,错误的配置可能导致安全漏洞或系统无法正常工作。