命令行技巧

几个新一代命令行工具

新一代命令行工具的特点是语法简单,符合直觉。他们大多使用 rust 或者 go 编写。

sd

sd 可以替代 sed。sd 是使用 rust 编写的,所以使用的正则引擎和你在 JS 和 Python 中熟悉的正则引擎是一致的,也就不需要各种奇奇怪怪的转义了。sd 还具有字符串模式,也就是关闭正则表达式,这也避免了一些转义的工作量。

安装

# 首先安装 rust,如果没有安装的话
~$ curl https://sh.rustup.rs -sSf | sh
~$ cargo install sd

使用


# 和 sed 的对比:
sd: sd before after
sed: sed s/before/after/g

# 字符串模式, -s 开启,可以看到括号就是括号
> echo 'lots((([]))) of special chars' | sd -s '((([])))' ''
lots of special chars

# 默认是正则模式
> echo 'lorem ipsum 23   ' | sd '\s+$' ''
lorem ipsum 23

# 使用正则分组
> echo 'cargo +nightly w

Linux 下分区并挂载磁盘

# 分区

“`
parted -s -a optimal /dev/sda mklabel gpt — mkpart primary ext4 1 -1s
“`

# 创建文件系统

“`
mkfs.ext4 /dev/sda1
“`

# 查看分区结果

“`
parted -l
“`

# 复制数据

首先挂载到临时分区

“`
mount /dev/sdb1 /mnt
“`

把之前的数据考进去

“`
# rsync -av /home/* /mnt/
OR
# cp -aR /home/* /mnt/
“`

校验数据

“`
diff -r /home /mnt
“`

删除老数据

“`
rm -rf /home/*
“`

“`
umount /mnt
“`

# 挂载

“`
mount /dev/sdb1 /home
“`

写入到 fstab 中

“`
blkid /dev/sdb1

/dev/sdb1: UUID=”e087e709-20f9-42a4-a4dc-d74544c490a6″ TYPE=”ext4″ PARTLABEL=”primary” PARTUUID=”52d77e5c-0b20-4a68-ada4-881851b2ca99″
“`

在 /etc/fstab 中增加

“`
UUID=e087e709-20f9-42a4-a4dc-d74544c490a6 /home ext4 defaults 0 2
“`

每一列的含义如下

“`

UUID – specifies the block device, you can alternatively use the device file /dev/sdb1.
/home – this is the mount point.
etx4 – describes the filesystem type on the device/partition.
defaults – mount options, (here this value means rw, suid, dev, exec, auto, nouser, and async).
0 – used by dump tool, 0 meaning don’t dump if filesystem is not present.
2 – used by fsck tool for discovering filesystem check order, this value means check this device after root filesystem.
“`

# 调整分区大小

首先使用 parted 打开对应的磁盘

“`
tiger@iZ8vbe91kz7sqlvkjdu8p6Z:~$ sudo parted
GNU Parted 3.2
Using /dev/vda
Welcome to GNU Parted! Type ‘help’ to view a list of commands.
(parted) select /dev/vdc
Using /dev/vdc
(parted) resizepart
Partition number? 1
Warning: Partition /dev/vdc1 is being used. Are you sure you want to continue?
Yes/No? yes
End? [107GB]? 1100G
(parted) print
Model: Virtio Block Device (virtblk)
Disk /dev/vdc: 1100GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags:

Number Start End Size Type File system Flags
1 1049kB 1100GB 1100GB primary ext4
“`

然后使用 resize2fs 重新调整分区大小

“`
resize2fs /dev/vdb1
“`

# 参考

1. https://www.tecmint.com/move-home-directory-to-new-partition-disk-in-linux/
2. https://www.tecmint.com/parted-command-to-create-resize-rescue-linux-disk-partitions/

pv – 在 Linux 下查看命令执行进度

pv 是 Pipe Viewer 的缩写,也就是管道查看器。挡在命令行执行命令的时候,可以通过使用 pv 来指导当前的进度。

# 使用

## 替换 cat

比如你要把一个日志打包好下载:

“`
% gzip -c access.log > access.log.gz
“`

可以改成

“`
% cat access.log | gzip > access.log.gz
“`

使用 pv

“`
% pv access.log | gzip > access.log.gz
611MB 0:00:11 [58.3MB/s] [=> ] 15% ETA 0:00:59
“`

## 使用多个 pv

可以使用多个 pv 来查看在不同阶段的速率

“`
$ pv -cN source access.log | gzip | pv -cN gzip > access.log.gz
source: 760MB 0:00:15 [37.4MB/s] [=> ] 19% ETA 0:01:02
gzip: 34.5MB 0:00:15 [1.74MB/s] [ <=> ]
“`

在上面的命令中,`-c` 是为了防止两个 pv 的显示混在一起。`-N` 表示名字。可以看到读取 access.log 的速率是 37.4 MB/s,而写入 gzip 文件的速率大概是 1.74 MB/s,我们大概也可以得出压缩率大概是21倍。

## 指定文件的大小

可以用下面这个命令压缩一个文件夹。

“`
$ tar -czf – . | pv > out.tgz
117MB 0:00:55 [2.7MB/s] [> ]
“`

在上面的例子中我们可以看到下面 gzip 一行的输出中没有百分比,因为 pv 没法知道 gzip 之后的最终大小,所以没有办法计算进度。可以使用 `-s` 指定大小。

“`
$ tar -cf – . | pv -s $(du -sb . | awk ‘{print $1}’) | gzip > out.tgz
253MB 0:00:05 [46.7MB/s] [> ] 1% ETA 0:04:49
“`

fzf – 命令行模糊查找工具

安装

在 mac 上直接 brew install fzf 就好了

使用

调用 fzf 命令

直接在命令行输入 fzf 开始模糊查找。

查找命令 匹配类型 说明
sbtrkt 模糊匹配 匹配sbtrkt
^music 前缀精确匹配 以music开头
.mp3^ 后缀精确匹配 以.mp3结尾
‘wild 精确匹配(quoted) 精确包含wild
!fire inverse-exact-match 不包含fire
!.mp3$ inverse-suffix-exact-match 不以.mp3结尾

|可以做or匹配, 比如 ^core go$|rb$|py$ 表示以core开头,以go或rb或py结尾的

按键

ctrl-j/k 或者 ctrl-n/p 或者箭头来上下选择
ctrl-c 或者 Esc 退出
Enter 选择
在多行模式,tab 和 Shift-tab 来标记文件

除了这些按键之外,还可以使用 --bind 绑定自己的按键,见下文

选中之后,fzf 的默认操作是打印这个文件名,这样我们还得打开,所以可以直接 vim $(fzf) 也就是使用 vim 打开我们选中的文件。

使用快捷键

Ctrl-T 快速选择当前目录文件,并把文件名打印出来
Ctrl-R 使用fzf来过滤history命令
ALT-C cd 进入选中的目录

自动补全

fzf 支持不少命令的自动补全功能,通过 ** 来触发。如果没有特殊支持某个命令的话,fzf会用文件来补全。

vim **<tab>
cd **<tab>
ssh **<tab>  从 /etc/hosts 中读取主机列表
unset **<tab>
export **<tab>
unalias **<tab>

kill -s TERM <tab>

这里我把触发按键设置成了 Ctrl-Y 比原生的触发更方便一点,如何配置见下文。

选项

--height xx% 默认情况下 fzf 占据了 100% 的屏幕
--reverse 提示符在上面
--bind 绑定命令
--preview 指定预览命令

默认情况下,在fzf中选中文件之后知识打印出这个文件名,可以使用bind来指定一些快捷键,来对文件的一些操作。

比如:

# Press F1 to open the file with less without leaving fzf
# Press CTRL-Y to copy the line to clipboard and aborts fzf (requires pbcopy)
fzf --bind 'f1:execute(less -f {}),ctrl-y:execute-silent(echo {} | pbcopy)+abort'

默认情况下,fzf 不会预览文件的内容,可以使用 –preview 指定,

# Use head instead of cat so that the command doesn't take too long to finish
fzf --preview 'head -100 {}'

语法高亮

fzf --preview '[[ $(file --mime {}) =~ binary ]] &&
                 echo {} is a binary file ||
                 (highlight -O ansi -l {} ||
                  coderay {} ||
                  rougify {} ||
                  cat {}) 2> /dev/null'

相关的环境变量

默认情况下,fzf 从 find * -type f 中读取文件列表,可以使用更好用的 fd 来替换。

export FZF_DEFAULT_OPTS='--height 40% --reverse --border' 这个变量来指定默认选项。
export FZF_DEFAULT_COMMAND='fd --type f' 来指定
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"

我的最终配置

[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh

export FZF_DEFAULT_COMMAND='fd --type f'
export FZF_CTRL_T_COMMAND='fd --type f'
export FZF_ALT_C_COMMAND='fd --type d'
export FZF_COMPLETION_TRIGGER=''
export FZF_DEFAULT_OPTS="--height 40% --reverse --border --prompt '>>>' \
    --bind 'alt-j:preview-down,alt-k:preview-up,alt-v:execute(vi {})+abort,ctrl-y:execute-silent(cat {} | pbcopy)+abort,?:toggle-preview' \
    --header 'A-j/k: preview down/up, A-v: open in vim, C-y: copy, ?: toggle preview' \
    --preview '(highlight -O ansi -l {} 2> /dev/null || cat {} || tree -C {}) 2> /dev/null'"
export FZF_CTRL_T_OPTS=$FZF_DEFAULT_OPTS
export FZF_CTRL_R_OPTS="--preview 'echo {}' --preview-window hidden:wrap --bind '?:toggle-preview'"
export FZF_ALT_C_OPTS="--height 40% --reverse --border --prompt '>>>' \
    --bind 'alt-j:preview-down,alt-k:preview-up,?:toggle-preview' \
    --header 'A-j/k: preview down/up, A-v: open in vim, C-y: copy, ?: toggle preview' \
    --preview 'tree -C {}'"
bindkey '^Y' fzf-completion
bindkey '^I' $fzf_default_completion
[ -f ~/.dotfiles/lib/fzf-extras.sh ] && source ~/.dotfiles/lib/fzf-extras.sh

fd – 更好的 find 命令(fd – A nicer find command)

fdhttps://github.com/sharkdp/fd ) 是 find 命令的一个更现代的替换。

fdhttps://github.com/sharkdp/fd ) is a modern and nicer replacement to the traditional find command.

对比一下 Some comparisons

查找名字含有某个字符的文件 Find a filename with certain string

OLD

-> % find . -name "*hello*"
./courses/hello_world.go
./courses/chapter_01/hello_world.go
./courses/chapter_01/hello_world
./examples/01_hello_world.go

NEW

-> % fd hello
courses/chapter_01/hello_world
courses/chapter_01/hello_world.go
courses/hello_world.go
examples/01_hello_world.go

使用正则表达式查找 Find files using regular expression

比如说查找符合 \d{2}_ti 模式的文件。find 使用的正则表达式非常古老,比如说在这里我们不能使用 \d,也不能使用 x 这种语法。因此我们需要对我们的正则表达式做一些改写。关于find支持的正则表达式这里就不展开了。

fd 默认就是使用的正则表达式作为模式,并且默认匹配的是文件名;而 find 默认匹配的是完整路径。另外

For example, let’s find a file whose name matches \d{2}_ti. find uses a very ancient form of regular expression. Neither can we use \d, nor can we use x. So we have to adjust our expression to these kind of limitations.

fd, by default, uses regular expression as patter, and matches filenames; on the other hand, find uses the -regex option to specify a regular expression, and it matches the whole path.

OLD

-> % find . -regex ".*[0-9][0-9]_ti.*"
./examples/33_tickers.go
./examples/48_time.go
./examples/28_timeouts.go
./examples/50_time_format.go
./examples/32_timers.go

NEW

-> % fd '\d{2}_ti'
examples/28_timeouts.go
examples/32_timers.go
examples/33_tickers.go
examples/48_time.go
examples/50_time_format.go

指定目录 Find in a specific directory

find 的语法是 find DIRECTORY OPTIONS;而 fd 的语法是 fd PATTERN [DIRECTORY]。注意其中目录是可选的。这点个人认为非常好,因为大多数情况下,我们是在当前目录查找,每次都要写 . 非常烦。

find follows the syntax find <directory> <options>; meanwhile, fd uses fd <pattern> [<directory>]. Note that the directory part is optional. AFAIK, this is very convenient. Most of the times, we are just trying to find something in the working directory, typing . each time is very annoying.

OLD

-> % find examples -name "*hello*"
examples/01_hello_world.go

NEW

-> % fd hello examples
examples/01_hello_world.go

直接执行命令 Execute the command without arguments

find 会打印帮助信息,而 fd 则会显示当前目录的所有文件。

find will print help message, fd will print all the files in current directory

OLD

-> % find
usage: find [-H | -L | -P] [-EXdsx] [-f path] path ... [expression]
       find [-H | -L | -P] [-EXdsx] -f path [path ...] [expression]

NEW

-> % fd
courses
courses/chapter_01
courses/chapter_01/chapter_1.md
courses/chapter_01/chapter_1.pdf
courses/chapter_01/hello_world
courses/chapter_01/hello_world.go

按后缀名查找文件 Find files by extension

这是一个很常见的需求,find 中需要使用 -name "*.xxx" 来过滤,而 fd 直接提供了 -e 选项。

It’s a very common use case. With find, you have to use -name "*.xxx", while fd provides -e option directly.

OLD

-> % find . -name "*.md"
./courses/chapter_01/chapter_1.md
./courses/chapter_1.md

NEW

-> % fd -e md
courses/chapter_01/chapter_1.md
courses/chapter_1.md

查找中过滤掉 .gitignore 中的文件 Exclude files listed in .gitignore

find 并没有提供对 .gitingnore 文件的原生支持,更好的方法可能是使用 git ls-files。而作为一个现代工具,fd 则默认情况下就会过滤 gitignore 文件,更多情况请查阅文档。

可以使用 -I 来包含这些文件,使用 -H 添加隐藏文件。

find does not natively support .gitignore files, a practical way would be using git ls-files. As a modern tool, fd ignores files listed in .gitignore and hidden files by default.

You could use -I to include those files, -H to also include hidden files.

OLD

-> % git ls-files | grep xxx

NEW

-> % fd xxx

排除某个文件夹 Exclude a directory

fd provides a -E option to exclude directories. You could put the directories in .fdignore, too.

OLD

-> % find . -path ./examples -prune -o -name '*.go'
./courses/hello_world.go
./courses/chapter_01/hello_world.go
./examples

NEW

-> % fd -E examples '.go$'
courses/chapter_01/hello_world.go
courses/hello_world.go

使用 xargs Using xargs

一般来说,如果使用管道过滤的话,需要使用 ‘\0’ 来作为字符串结尾,避免一些潜在的空格引起的问题。

find 中需要使用 -print0 来调整输出 ‘\0’ 结尾的字符串,在 xargs 中需要使用 -0 表示接收这种字符串。而在 fd 中,和 xargs 保持了一直,使用 -0 参数就可以了。

If you are using pipes to filter results, using \0 other than \n would be a better option to avoid some potential problems.

find with -print0 will output \0-terminated strings, and xargs‘s option is -0 to process them. fd chooses -0 as its option, which is consistent with xargs.

OLD

-> % find . -name "*.go" -print0 | xargs -0 wc -l
       7 ./courses/hello_world.go
       7 ./courses/chapter_01/hello_world.go
      50 ./examples/07_switch.go
...

NEW

-> % fd -0 -e go | xargs -0 wc -l
       7 courses/chapter_01/hello_world.go
       7 courses/hello_world.go
       7 examples/01_hello_world.go
...

总之,fd 命令相对于 find 来说相当简单易用了

As you can see, using fd can save you a lot of keystrokes.

PS

使用 exec Using exec

OLD

-> % find . -name "*.md" -exec wc -l {} \;
     114 ./courses/chapter_01/chapter_1.md
     114 ./courses/chapter_1.md

NEW

You could also omit the

-> % fd -e md --exec wc -l {}
     114 courses/chapter_1.md
     114 courses/chapter_01/chapter_1.md

命令行的一些小技巧

最近工作中经常用到的一些组合命令,本来想提交到 commandlinefu.com 上,但是忘记了密码,怎么也登录不上去了,记到这里吧

* 脚本所在的目录

“`
dirname $0
“`

* 文件夹下面按照占用空间大小排序

“`
du -sh `ls` | sort -rh
“`

* 返回上一个目录

“`
cd –
“`

* 显示所有的日志的最后几行

“`
tail *
“`

* set

“`
set -x # 显示每个执行的命令
set -e # 当有程序返回非0值时直接退出,相当于抛出异常
“`

* here doc

“`
cat << EOF > /tmp/yourfilehere
These contents will be written to the file.
This line is indented.
EOF
“`

* 删除包含某个关键字的所有行

“`
fd -t f -0 | xargs -0 sed -i /KeyWord/d
“`

* 在 shell 中,所有字符串都要用引号包围

Always quote strings in bash. If you string is empty and you are testing it with == or !=, then there will be a “== is not uniary operator” error

* 替换一个文件夹下的所有文件

“`
fd . -t file -0 | xargs -0 sed -i -e ‘s/make_redis_client/create_redis_client/g’
“`
from: https://stackoverflow.com/questions/6758963/find-and-replace-with-sed-in-directory-and-sub-directories

shell 编程教程

# 变量和值

variables are referenced by $var or ${var}. Global variables are visible to all sub bash sessions, and are often called env variables, local variables are only visible to local session, not subsessions.

Global variables can be viewed as `env`, and can be created by `export`.

TEST=testing; export $TEST # or
export TEST=testing # NOTE: no $

## Useful variables

“`
HOME Same to ~
IFS
PATH Search path
EUID User id
GROUPS Groups for current user
HOSTNAME Hostname
LANG
LC_ALL
OLDPWD
PWD
“`

## 定义和使用变量

定义变量,注意因为 shell 中的语法使用空格作为命令分割的限制,等于号前后不能加空格。

“`
FOO=bar
“`

使用变量,需要添加上 `$` 符号。

“`
echo $FOO
“`

字符串在双引号中可以直接插入,这时候要加上大括号来指示变量名的起始位置。

“`
echo “${FOO}xxx”
“`

变量默认实在当前的回话中可见的,而不会作为环境变量传递给调用的命令。可以使用 export 导出变量,或者在命令前加上指定的环境变量。

“`
-> % cat env.py
import os
print(‘FOO env variable is: ‘, os.environ.get(‘FOO’))

-> % python3 env.py
FOO env variable is: None

-> % FOO=bar python3 env.py
FOO env variable is: bar
“`

使用 export

“`
-> % export FOO=bar
-> % python3 env.py
FOO env variable is: bar
“`

## 一些有用的内置变量

“`
$HOME 家目录,比如 /home/kongyifei
$IFS 默认的分隔符,和 for 循环紧密相关
$PATH 搜索路径,当你执行 ls 的时候,shell 会在这个变量中查找 ls 命令
$EUID 当前有效用户 ID
$LANG
$LC_ALL
$OLDPWD 上一个工作目录
$PWD 当前工作目录

“`

## 数组

使用小括号来定义一个数组,关于 for 循环随后会讲

“`
A=(1 2 3)

for el in ${A[@]}; do
echo $el
done
“`

## 字符串操作

大括号里面的字符串会被展开成独立的字符串

“`
% echo {1,2,3,4}
1 2 3 4
% mkdir -p test/{a,b,c,d}{1,2,3,4}
% ls test/
a1  a2  a3  a4  b1  b2  b3  b4  c1  c2  c3  c4  d1  d2  d3  d4
% mv test/{a,c}.conf # 这个命令的意思是:mv test/a.conf test/c.conf
“`

切片: `${string:start:length}`

默认值 `${var:-default}`

设定值 `${var:=default}`

长度 `${#var}`

### 字符串 Expansion and slice

[zorro@zorrozou-pc0 bash]$ mkdir -p test/zorro/{a,b,c,d}{1,2,3,4}
[zorro@zorrozou-pc0 bash]$ ls test/zorro/
a1  a2  a3  a4  b1  b2  b3  b4  c1  c2  c3  c4  d1  d2  d3  d4

[zorro@zorrozou-pc0 bash]$ mv test/{a,c}.conf
这个命令的意思是:mv test/a.conf test/c.conf

${string:start :length} string slice

default value: ${var:-default}
set value: ${var:=default}

${#var} get variable length

# Redirection

input: <, output >, append >>

cat > file << EOF this line will be redirected to file EOF # pipe pipe commands will be run simultaneously, but the second command will wait for the input # Sub shell use $(expression) # 控制语句 ## 条件语句 if 语句成立的条件是 `expr` 返回值为 0。 ``` if expr; then statement; elif expr; then statement; else statement; fi ``` ## test command 虽然可以使用任意的语句作为判断条件,不过我们一般情况下都是用 `[` 这个命令来作为判断条件的,需要注意的是 `[` 并不是一个语法,而是一个命令。不过由于 `[` 这个上古命令实在功能太少,现在一般采用 `[[` 来作为判断条件。 ``` if [[ "a" == "b" ]]; then echo "wtf" else echo "meh" fi ``` `[[`支持的条件有 1 数值比较, 仅限整数,注意不能使用 `>` `<` 等符号。 ``` n1 -eq n2 equal n1 -ge n2 greater or equal n1 -gt n2 greater n1 -le n2 less or equal n1 -lt n2 less n1 -ne n2 not equal ``` 2 字符串比较 Note: Variables may contain space, so the best way to comparison is to add quotes: `"$var1" = "$var2"` ``` str1 == str2 equal str1 != str2 not equal str1 < str2 less str1 > str2 greater
-z str zero
-n str not zero length
“`

3 file comparison

-d is directory?
-e exist?
-f is regular file?
-r exist and readable?
-s exist and has content
-w exist and writable
-x exist and executealbe
-O exist and owned
-G exist and in same group
file -nt file2 newer than
file1 -ot file2 older than

## case

case var in
parttern | pattern2) commands;;
pattern3) commands2;
*) default commnads;;
esac

## Loops

### foreach 语句

for var in list; do
echo $var
done

其中 list 可以是一个数组,也可以是一个被 $IFS 分割的字符串。默认情况下,$IFS 是 ” \n\t”。其中包含了空格。

如果要覆盖 IFS,一般这样使用:

OLDIFS=$IFS
IFS=”\n” # new seperator
# do things
IFS=$OLDIFS

### while-loop

until/while expr; do
# commands
done

### pipe

the result of a for loop is pipe-able to other command

“`
for city in beijing shanghai; do
echo $city is big
done > cities.txt
# will save the result in cities.txt
“`

# 输入输出

## 命令行参数

parameters to a script can be obtained as $1, $2, $3…。 $0 is the script name, remember to check whether the parameter is empty. $# is the number of parameters(without script name).

“`
$0 script name / function name
$1…$x command line arguments / parameters
$# number of arguments(without $0)
$* all parameters as a string
$@ all parameters as a string list
“`

### shift

processing parameters using shift,

while [ -n “$1” ]; do
case “$1” in
-a) echo “option -a” ;;
–) shift
break;;
*) echo “$1” is not a option ;;
esac
shift
done

## read

read OPTIONS VARNAME read input to variables

– read -p Prompt
– read -t timeout
– read -s hide input

we can use read to read file or stdin

## redirection

2> redirect STDERR
m>&n redirect fd m to fd n’s associated file

Note: you have to use command >> command.log 2>&1 (put 2>&1 at the end), since this means redirect 2 to 1’s
in a scirpt
exec 2> filename # reopen stdout to filename

# Signal

trap commnad signal is used to handle signals in shell

# Functions

有两种定义函数的方式

“`
function name {
# function body
}

foo() {
# function body
}
“`

要调用上面这个函数,直接就输入

“`
foo
“`

就好了

## return

shell functions behave like a small script, and it does NOT return a computed value…It retures a exit code, which is between 0 and 255. if no return is specified, the exit code of last command will be returned

You can read the return value by $? like any normal commands

the right way to to return a value from function, you will have to echo out the value, and put the function is subshell

“`
function foo {
# do some compute
echo $result;
}

retval=$(foo)
“`

Note: any thing that echos in the function body will be captured, so please keep that from happen

## parameters

like a shell script, $0 holds the function name, $1 … $9 holds the parameters, $# is the num of parameters

## local variables

use `local` to declare local variables

# alias

“`
alias new_name=’command string’
$ \command # bypass alias
“`

# debugging

DEBUG macro

# multiprocess

PID_ARRAY=()
for file in filelist; do
md5sum file &;
PID_ARRAY+=(“$!”)
done
wait ${PID_ARRAY[@]}

linuxbrew

It can be installed in your home directory and does not require root access.

ruby -e “$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install)”
PATH=”$HOME/.linuxbrew/bin:$PATH”