1
头图

SHell 与远方

在 BASH 上做一些与远端机器之间的传递。🦎🦎

Note

万一遇上一些名词,我没解释,你又头回听,从而导致你看不懂我在说啥,那么,你可以自个儿先查查它的意思,或者等我的《稍后补充》如果你相信我不会 咕咕咕 的话。。。。🕊🕊🕊

0x00

众所周知,有个命令叫 rsync

不知道?那你可能听过 scp ,都不知道也没事,正好。

一般,你想往远处( 10.60.111.124 )用指定用户( heihei )的指定目录( $HOME/yoyo )里,传过去一个文件( oho.iex ),这个文件就在当前目录。

(对本机来说也就是上传本地文件到远端某目录)

那么一般的做法就是这样:

rsync -avz -- oho.iex heihei@10.60.111.124:yoyo

这样的话能做到以下几件事:

  • 完全相同的文件如果已经在目标目录存在,这个文件就不会被传输;文件名相同文件内容不同则会覆盖。
  • 选项 a 使得这个文件的元数据信息(创建时间、修改时间、文件归属等这些并不存在文件内容里的信息)得到保留;选项 z 会开启压缩即上传前先压缩过了网络后再自动解压,这样就能省流;选项 v 则是显示传输过程,有没有都不影响传输结局。

由于要上传的文件就在当前目录,所以 -- 后面的 oho.iex 连相对路径的 ./ 也不用写,更不需要写绝对路径。又由于文件夹正好在这个用户的 $HOME 目录下,所以冒号 : 后面也不用写绝对路径。事实上,如果只有冒号 : 后面空着,文件就会被上传到所使用用户 heihei 的家目录。(但如果你冒号都没写,这个工具就认为你只是把文件从本地传到本地,就会当前目录建立一个 heihei@10.60.111.124 文件夹然后把文件复制进去。。。)

从远端( 10.60.111.128 )通过指定用户( haha )把指定文件( /etc/profile.d/miaomiao.sh )拉到本地指定目录(当前目录 . ),也是类似的。

(对本机来说也就是下载指定文件到本地指定目录)

你可以这样写:

rsync -avz -- haha@10.60.111.128:/etc/profile.d/miaomiao.sh .

这个应该不用讲解了吧。。。

但是,还有个问题:这个软件需要对面也装了才行。而且还得不能出错。

0x01

如果只关心文件内容的传递,只要你们两边都有 SSH ,那么大可以像这样做:

拉取(对本机来说算下载)

ssh haha@10.60.111.128 -- 'cat /etc/profile.d/miaomiao.sh' > miaomiao.sh

推送(对本机来说算上传)

cat oho.iex | ssh heihei@10.60.111.124 -- 'cat > yoyo/oho.iex'

当然,还可以自行选定压缩工具(需要两边都安)。这里以 xz 举例:

拉取(对本机来说算下载)

ssh haha@10.60.111.128 -- 'cat /etc/profile.d/miaomiao.sh | xz -T0 --best' | xz -dc > miaomiao.sh

推送(对本机来说算上传)

(cat oho.iex | xz -T0 --best) | ssh heihei@10.60.111.124 -- 'cat | xz -dc > yoyo/oho.iex'

另外,现在不是有很多玩容器化的吗, docker 直接把 Go 这个语言带起来了。它的镜像支持导出 save 为文件和从文件和加载 load

一般的做法就是远端导出一个文件起个名字叫 啥啥.tar (导出的的确是 Tar 格式不管你给它文件名啥扩展名)然后挪到另一台上再导入。

现在我们可以让它中途压缩,并且全程管道,不用专门先生成文件,不留多余的东西。

(这也多亏 docker 命令支持从标准输入加载镜像或从标准输出写出文件)

(这里的劳模镜像就用 docker.io/trinodb/trino 了。这个工作主要是用能连互联网的机器往不通互联网的机器传送镜像。)

## 远端镜像来本机:
ssh haha@10.60.111.128 -- 'docker save docker.io/trinodb/trino | xz -T0 --best' | xz -dc | docker load
## 本机镜像到远端:
docker save docker.io/trinodb/trino | xz -T0 --best | ssh haha@10.60.111.128 -- 'xz -dc | docker load'

## 甚至可以从这个远端直接送到那个远端!啊对!普通文件当然也可以这么玩儿!
ssh haha@10.60.111.128 -- 'docker save docker.io/trinodb/trino | xz -T0 --best' | ssh heihei@10.60.111.124 -- 'xz -dc | docker load'

0x02

不过, ssh 是要密码的(基于 SSH 协议的工具都要(如 rsync 就是一个))。而有的时候其实不便于输密码。怎么办呢?

可以用 sshpass

(查了一下,这应该是个用 C 写的开源工具,不过貌似也有 PY 版本。。。无所谓,命令就能安,这里也有各种系统的包。)

用法很简单:把上面的 ssh 换成 sshpass -p $xx -- ssh 就好了,其中变量 xx 已经存入了对应节点对应用户的密码。

几个示例:

## 镜像远到本
sshpass -p $hahapassword -- ssh haha@10.60.111.128 -- 'docker save docker.io/trinodb/trino | xz -T0 --best' | xz -dc | docker load
## 文件远到远
sshpass -p $hahapassword -- ssh haha@10.60.111.128 -- 'cat /etc/profile.d/miaomiao.sh | xz -T0 --best' | sshpass -p $heiheipassword -- ssh heihei@10.60.111.124 -- 'cat | xz -dc > /etc/profile.d/miaomiao.sh'
## rsync 也能用 sshpass
sshpass -p $heiheipassword -- rsync -avz -- oho.iex heihei@10.60.111.124:yoyo

0x03

但是 rsync 还能一下传一个文件夹呢!

确实,上面这种最原始的手段只是传单个文件而已。要传文件夹的话,大可以先打包成 Tar 格式的文件,然后再传单个文件就好:

(用 SSH 协议的时候如果删掉指定的远端登录用户名和 @ 的话就等于用本机正在用的当前用户登录,这需要远端有相同名称的用户并且你还知道密码。)

## 远到本 当前目录
sshpass -p $xx -- ssh 10.20.202.233 -- 'tar -cf - -- /etc/profile.d' | tar -x 
## 远到本 指定目录中途有压缩
sshpass -p $xx -- ssh 10.20.202.233 -- 'tar -cf - -- /etc/profile.d | xz -T0 --best' | xz -dc | tar -x -C $HOME/documents
## 本到远 完全同上的行动
tar -cf - -- /etc/profile.d | xz -T0 --best | sshpass -p $xx -- ssh 10.20.202.233 -- 'xz -dc | tar -x -C $HOME/documents'
## 远到远 完全同上的行动
sshpass -p $xx -- ssh 10.20.202.233 -- 'tar -cf - -- /etc/profile.d | xz -T0 --best' | sshpass -p $xxx -- ssh 10.20.203.244 -- 'xz -dc | tar -x -C $HOME/documents'

务必注意:上面的例子会在目标目录下放入 etc 这个目录。如果只是想挪一个目录而不保留它原本所在绝对路径的信息,就请这样:

sshpass -p $xx -- ssh 10.20.202.233 -- 'cd /etc && tar -cf - -- profile.d' | tar -xC . 
(cd /etc && tar -cf - -- profile.d) | sshpass -p $xx -- ssh 10.20.202.233 -- 'tar -x' 

## -C . 等于不写

## 之所以先 cd 到对应路径(即便是相对路径)的 `dirname` 是因为
## tar 在压缩时会根据 -- 后面的值直接决定存储在 Tar 包内的各项的名称
## (这个名称就是包含路径字符串在内的名称字符串)

## 这个特性使得咱们用户能直观地控制什么程度的相对路径存在在一个 Tar 包内而又不必把这个相对路径下的一切都打包
## 这个特性也就使得下面的做法成为了可能(当然也让更多奇怪的操作成为可能)

如果想要保留原来的绝对路径来移动,可以这样:

sshpass -p $xx -- ssh 10.20.202.233 -- 'tar -cPf - -- /etc/xxx.d' | tar -xP 
tar -cPf - -- /etc/xxx.d | sshpass -p $xx -- ssh 10.20.202.233 -- 'tar -xP' 

## ssh 的 -- 后面的命令里 若没有譬如特殊字( && || | & ; )符的话 单引号可以不写
## tar 的 -P 选项使得在操作(它对压缩和解压都能有效)的时候不删除绝对路径的头 压缩解压都带上的话就可以依据存在 Tar 包里的原绝对路径信息在新节点确保目标位置还是原来那个

其实只要能变成 Tar 包,剩下的就跟镜像的转义完全一样了。

一般的使用:

ahaha@100.202.103.104$HOME 目录下的 abc 目录挪到本机当前目录 .

ssh ahaha@100.202.103.104 -- 'tar -cf - -- abc' | tar -x -C .

把当前目录 . 下的 abc 目录挪到 ahaha@100.202.103.104$HOME 目录下

tar -cf - -- abc | ssh ahaha@100.202.103.104 -- 'tar -x -C .'

0x04

还可以批量搞事情。

比如你在本地某个目录,你想把里面所有文件名 .docx 结尾的文件传输给指定远端目录的特定目录里。

首先要手动建立目录:

sshpass -p $xx -- ssh haha@10.20.103.237 -- mkdir -p to_kiki

然后这样就能传了:

ls *.docx | 
    xargs -i -P0 -- bash -c "cat {} | sshpass -p $xx -- ssh haha@10.20.103.237 -- 'cat > to_kiki/{}'"

当然,批量拉取也是可以的(来源文件夹肯定已经是有的所以不再建立了):

sshpass -p $xx -- ssh haha@10.20.103.237 -- ls kiki | 
    xargs -i -P0 -- bash -c "sshpass -p $xx -- ssh haha@10.20.103.237 -- 'cat kiki/{}' > {}"

思路就是拼接命令,交给 xargs 批量执行。

这些示例没开压缩,需要的话自己举一反三。其实没必要,只要东西不太大。

另外,也可以不用 xz 这个命令来压缩,也可以用 zstd ,后者似乎更适合于这种场景。(本来用 .tar.xz 存安装包的 Arch 团队经过测试后改用这个压缩算法(级别是 18 ),速度大幅提升同时压缩率则只是差了微乎其微一点点。

0x05

还可以这么着:

sshpass -p $xx -- ssh haha@10.20.103.237 -- 'wget https://ghproxy.com/https://github.com/rustdesk/rustdesk/releases/download/1.1.6/rustdesk-1.1.6.exe -O /dev/stdout' > rustdesk-1.1.6.exe

如果你本机不能连互联网,而某个服务器可以的话。

反过来也是可以的:

wget https://ghproxy.com/https://github.com/rustdesk/rustdesk/releases/download/1.1.6/rustdesk-1.1.6.exe -O /dev/stdout | sshpass -p $xx -- ssh haha@10.20.103.237 -- 'cat > rustdesk-1.1.6.exe'

备注

其实知道命令该怎么切会更好理解。答案是,先按照空格(引号外的没转义的)切,再按照管道符 | 切。

双横线 -- 只是 对命令传入参数 (args) 的一部分,可以省略。支持不省略的时候建议写上,它的出现一般意味着,左边是 选项 (option) 的部分,右边是 无选项参数 (para) 的部分。可以没有,有的话则能帮你对一个命令进一步划分层次。

哦对了,其实对于 ssh 来说,若要用 -- 的话,更完整地写就不是 ssh 10.28.101.202 -- hostname -i 而是 ssh -- 10.28.101.202 -- hostname -i 了。但,后者也是只在像下面这样的时候才具有区分层次的意义:

ssh -o StrictHostKeyChecking=no -- 10.28.101.202 -- hostname -i
## 这个 -o StrictHostKeyChecking=no 用来避免初次连接的时候问你 yes no ,而是直接信任对应地址。
## 一般是在,远端已明确是可信的,并且需要自动化的脚本的时候,会用到。

另外,更多 SHell 知识可以看这个

0x06

多说一句,就是,通过管道给 ssh 命令的话,对面的相应命令是否有反应,两边命令对于流式处理的支持而定的。

比如,对于这个情况:

while sleep 1s;do echo xxxx ; done| ssh 10.233.3.2 -- cat - # 短横线省略也是一样的
(echo while True: print\(114514111\) | python3 -) | ssh 10.233.3.2 -- cat - # 短横线省略也是一样的

(这两个用哪个试都行。)

你会发现,右边那个远端的 cat 传回来的标准输出是一直输出着的。这说明,它并没有一定要等待这边的输出完全给到那边的输入、那边才会启动指定的程序。

而且,如果远端的 cat 写错了,正好是不存在的命令,也会立刻提示错误。

但是,在下面的番外里面,远端的命令应当是 docker load 的话,给改成错误的不存在的命令,你会发现,它要等镜像数据传递完毕的时候才给你说命令错了,也就是说数据是先完全过去,然后才刚刚开始触发那边执行咱们在 -- 后指定给那边的命令。

这个我目前的结论就是一开始提到的,但也不一定就是这样。应该是可以再多加研究的。


分享注明来源: https://segmentfault.com/a/1190000040324158

番外

这部分不涉及本文主题,但有一定相关性。

玩 Docker 镜像

正文中提到过 Docker 镜像,由提到过 Xargs 。这里给个批量导出导入的例子。

批量下载

docker search cloudera | fgrep loicmathieu | awk \{print\$2\} | xargs -i -P0 -- bash -c 'fn () { docker pull -- {} >/dev/null && echo ok,${1:-0}: {} || { echo err,${1:-0}: {} ; exec bash -c "$(declare -f fn) ; fn $((${1:-0} + 1))" ; } ; } ; fn'

上面的 Bash 会同时去下 docker search cloudera | fgrep loicmathieu 会列出来的所有镜像(标签都是空着的)

会印出报错,不会印出进度,会报告成功还是失败,如果失败会自行重试。

注意:上面代码里的 fn 不是匿名函数而是命名函数,函数名叫 fn ,因为我懒得想名字……你也可以改成别的,不改不影响功能。

想要用它的话,真正要改的也就是前面的 docker search cloudera | fgrep loicmathieu | awk \{print\$2\} 这部分(你可以直接执行以下看看这部分的效果),它的标准输出应当被确保是一行一个能放在 docker pull 后头的字串的样子。

比如你还可以用这样一条命令:

docker search ambari | awk /hortonworks/\{print\$2\} | xargs -i -P0 -- bash -c 'fn () { docker pull -- {} >/dev/null && echo ok,${1:-0}: {} || { echo err,${1:-0}: {} ; exec bash -c "$(declare -f fn) ; fn $((${1:-0} + 1))" ; } ; } ; fn'

上面的代码只是我顺手写的,理解后文不需要搞懂。当然你也可以尝试去搞懂一下。。。🦎🐊🦎总之这部分重点在下面。

批量导出

假设你已经有一个文件: used-images.imglist

文件里里面每一行是形如这样的内容: 101.202.103.104/ahahaha/racket:7.9-cs

其中:

  • 101.202.103.104 是它来自的 仓库 (即所谓 源 (repo) )的地址,可以是 IP 号,可以是 Hostname 。
  • ahahaha 是它的 作者 名称(或许也会是项目名)。
  • racket 是镜像名。
  • 7.9-cs 是标签 (Tag) 名,用于表示此镜像的迭代版本。
代码示例

导出的话,最好有个导出文件夹。我这里就在上面那文件所在目录建立新的文件夹并进去:

mkdir img-saves-d && cd img-saves-d

然后执行这个就好了:

cat ../used-images.imglist |
    xargs -i -P1 -- bash -c '

echo "pulled: $(docker pull -q -- "{}")" &&
mkdir -p -- "$(dirname "{}")" &&
docker save -- "{}" |
    xz -T0 --best > "{}.tar.xz" &&
echo "save,ok: {}" ;' ;
这里的压缩引擎没用事实上更优秀的 Zstd 而是用了 Xz ,主要是因为并非所有环境都有安装前者,且后者在压缩率上是最优秀的,不优秀只是在速度。另外,不论速度还是压缩率, Zstd 基本都能碾压 Gzip (主要是 Gzip 也实在是太老了而 Zstd 又太新了)。

它是这么写出来的?

  1. 先是写了一个具体的例子:

    docker pull -q -- 101.202.103.104/ahahaha/racket:7.9-cs &&
    mkdir -p 101.202.103.104/ahahaha &&
    docker save 101.202.103.104/ahahaha/racket:7.9-cs | xz -T0 --best > 101.202.103.104/ahahaha/racket:7.9-cs.tar.xz
    
  2. 然后把相同的部分抽象一下:

    docker pull -q -- {} &&
    mkdir -p $(dirname {}) &&
    docker save {} | xz -T0 --best > {}.tar.xz
    

    其中需要单独验证一下:

    dirname 101.202.103.104/ahahaha/racket:7.9-cs 
    ## 不论啥目录都能输出 101.202.103.104/ahahaha/racket:7.9-cs 
    ## 的话,那就没问题。
    
  3. 然后给放在 xargs 后面:

    echo 101.202.103.104/ahahaha/racket:7.9-cs |
        xargs -i -- bash -c '
    
    docker pull -q -- {} &&
    mkdir -p $(dirname {}) &&
    docker save {} | xz -T0 --best > {}.tar.xz '
    

    可以把 bash -c 换成 echo 看看打印出来的是不是合乎你设想的命令,即是不是合乎上面「具体的例子」。不合乎就要想办法改到合乎。

    没问题,就把 echo 101.202.103.104/ahahaha/racket:7.9-cs 换成 cat ../used-images.imglist ;直接分别执行以下这两个小命令可以看出二者的区别。然后换了以后,也可以先把 bash -c 换成 echo 执行看看:

    cat ../used-images.imglist |
        xargs -i -- echo '
    
    docker pull -q -- {} &&
    mkdir -p $(dirname {}) &&
    docker save {} | xz -T0 --best > {}.tar.xz '
    
  4. 再根据自己的特殊需要增加边边角角就可以了。

    我这是为了打印效果做成了上面那样:

    cat ../used-images.imglist |
        xargs -i -P1 -- bash -c '
    
    echo "pulled: $(docker pull -q -- "{}")" &&
    mkdir -p -- "$(dirname "{}")" &&
    docker save -- "{}" |
        xz -T0 --best > "{}.tar.xz" &&
    echo "save,ok: {}" ;' ;
    

    这里 xargs 的并发度选项之所以是 -P1 ,是因为,如果 -P0 的话,就会发生 断开的管道 的情况(错误提示这么写的),从而有的不能完成导出。

哦对了,最好在导出之前就改好 repo 的部分:改成适用于目标地的 repo如果是 IP 号的话可能确认起来会比较费劲(不过应该也不费太多)

批量导入

假设你已经有一个文件夹: img-saves-d

进入里面、并执行 find . -type f 的话,列出的是形如这样的一行行: ./101.202.103.104/ahahaha/racket:7.9-cs.tar.xz

对于那个 find 命令:

  • 那个 . 是指当前目录。
  • 那个 -type 是一个叫 断言 而不是 选项 的东西,不过等同于选项的作用;而 -type f 表示只会列出「普通文件」。

    关于「普通文件」:

    • 可以使用 stat 文件名 查看(比如 stat /dev/stdout ),其中会有表示文件类型的位置。
    • 也可以使用 ls -l 文件名 查看(比如 ls -l /dev/stdout ),其中普通文件所在行的第一个字符是 -

对于列出内容:

  1. 首先:列出的只是在当前目录下的所有文件的相对路径。
  2. 其次:只要是按照上面的导出部分的逻辑做的导出,得到的导出包所在的目录结构就会是像上面示例的那样。
  3. 这个目录结构不是强制性的。因为,正常导出的情况下,完整的 作者 的信息已经包括在了被导出的包里。
代码示例
( onece_load_push () 
{
    be_loadpushed="$1" &&
    errtimes="${2:-0}" &&
    
    push_loaded_cmdmkr ()
    { awk '/Loaded image:/{split($0,msgarr,"Loaded image: ");print"docker","tag","--","'"'"'"msgarr[2]"'"'"'","'"'$be_loadpushed'"'","&&","docker","push","--","'"'$be_loadpushed'"'"}' | tee -a /dev/stderr ; } &&
    
    xz -dc -- "$be_loadpushed".tar.xz |
        docker load -q |
        push_loaded_cmdmkr |
        
        bash &&
        { echo ok,$errtimes: "$be_loadpushed" >&2 ; } || 
        {
            echo err,$errtimes: "$be_loadpushed" >&2 ;
            exec bash -c "$(declare -f onece_load_push) ; onece_load_push '$be_loadpushed' $((errtimes+1))" ;
        } ;
} &&
(find * -name \*.tar.xz -type f | sed -e s/.tar.xz\$// -- -) |
    xargs -P0 -i -- bash -c "$(declare -f onece_load_push) ; onece_load_push '{}' " )

我知道看起来很复杂,下面会讲一下它是怎么来的……


先试一个简单的:

xz -dc -- ./101.202.103.104/ahahaha/racket:7.9-cs.tar.xz | docker load

其中,选项部分的 -dc 等同于写成 -d -c 这样,而里面的选项 -c 等同于 --stdout 。这些都可以在 xz --help 的输出中查到。

上面能成功就可以继续,不能,意味着导出的包有问题。多半因为里面其实是空的。

那么全上应该也是可以的:

如果载入的镜像没改好 repo ,所有的 push 命令你都不会执行成功。不过之后会给出兼容这个需求的方案。

find . -name \*.tar.xz -type f | xargs -P0 -i -- bash -c 'xz -d --stdout -- "{}" | docker load'

注意先执行 cd img-saves-d 。这个在最开始执行 find . -type f 的那里就已经说过了(那个「进入里面」指的就是这个 cd 命令)。

成功的话,你应该总能看到,有那么一行像这样的输出:

Loaded image: 101.202.103.104/ahahaha/racket:7.9-cs

并且即便是在后面对 docker load 用上了 -q 选项,像这样的一行仍会出现在标准输出。成,那就用上:

xz -dc -- ./101.202.103.104/ahahaha/racket:7.9-cs.tar.xz | docker load -q | awk '/Loaded image:/{split($0,linearr,"Loaded image: ");print linearr[2]}'

这样就只会输出 101.202.103.104/ahahaha/racket:7.9-cs 了;那么接下来的命令就利用此来拼,改改上面的代码:

xz -dc -- ./101.202.103.104/ahahaha/racket:7.9-cs.tar.xz | docker load -q | awk '/Loaded image:/{split($0,msgarr,"Loaded image: ");print"docker","push","--",msgarr[2]}'

这样,输出的就是我想要的命令了。再补上 | bash 就能执行它,或者还可以像这样多补上点东西:

xz -dc -- ./101.202.103.104/ahahaha/racket:7.9-cs.tar.xz | docker load -q | awk '/Loaded image:/{split($0,msgarr,"Loaded image: ");print"docker","push","--",msgarr[2]}' | tee -a /dev/stderr | bash

其中,多补上的 | tee -a /dev/stderr 使得命令还能输出到标准错误一份,从而不会进入管道而是打印出来,从而能在控制台盯着的时候多少有个数。

如果需要改 repo 建议导出之前就改好,否则这里就不能像这样,导入后直接 docker push 了。

接下来只需要把该替换的替换掉,然后填入 xargs 命令的参数中正确的位置就好。

如果不需要改名

这是我的两个写成的例子。

( push_loaded_cmdmkr () { awk '/Loaded image:/{split($0,msgarr,"Loaded image: ");print"docker","push","--",msgarr[2]}' | tee -a /dev/stderr ; } ; find . -name \*.tar.xz -type f | xargs -P0 -i -- bash -c "$(declare -f push_loaded_cmdmkr) ; xz -dc -- '{}' | docker load -q | push_loaded_cmdmkr | bash && echo 'ok:' '{}' || echo 'err:' '{}' " )

上面这个可以直接用,如果 docker push 的部分失败,就直接报出错误了。

如果你确定你的镜像名称是没问题的,而你的 docker push 又是会由于网络原因可能会中途失败的,那么可以使用下面的代码:

( onece_load_push () 
{
    be_loadpushed="$1" &&
    errtimes="${2:-0}" &&
    
    push_loaded_cmdmkr ()
    { awk '/Loaded image:/{split($0,msgarr,"Loaded image: ");print"docker","push","--",msgarr[2]}' | tee -a /dev/stderr ; } &&
    
    xz -dc -- "$be_loadpushed" |
        docker load -q |
        push_loaded_cmdmkr |
        
        bash &&
        { echo ok,$errtimes: "$be_loadpushed" >&2 ; } || 
        {
            echo err,$errtimes: "$be_loadpushed" >&2 ;
            exec bash -c "$(declare -f onece_load_push) ; onece_load_push '$be_loadpushed' $((errtimes+1))" ;
        } ;
} &&
find . -name \*.tar.xz -type f | xargs -P0 -i -- bash -c "$(declare -f onece_load_push) ; onece_load_push '{}' " )

它会在失败发生时自动重试,直到成功,并且全程你可以根据打印的信息看得到它失败的次数。想要简略的输出可以在后面的括号外面加上这个: >/dev/null

如果需要改名

其实根据上面的简单修补即可。

当然了,这部分要求你必须是按照导出部分形成的目录结构来存放 tar.xz 镜像文件的。

首先,辨析一下这两者的输出区别:

  • find . -name -type f :这个是作用于一个目录的, . 是当前目录。
  • find * -name -type f :这个是作用于当前目录下所有文件的,通配符 * 表示不论叫啥的任何文件名(目录属于一种特殊类型文件故目录名本质上也只是目录文件的文件名)

用后者,再去掉扩展后缀,就可以作为合法的 Docker 镜像名了。

去掉后缀的命令可以用 sed -e s/.tar.xz\$// -- - 。比如:

echo xxx.tar.xz.tar.xz | sed -e s/.tar.xz\$// -- - # will out: xxx.tar.xz

然后,试验一下改名,其实是给已加载镜像增加别名的功能: docker tag

docker tag -- 101.202.103.104/ahahaha/racket:7.9-cs a.b/ahahaha/racket:7.9-cs

然后查看镜像就会发现有新增的别名。

命令 docker tag 用文件系统类比的话,就是为同一个文件增加了 硬链接

那么,就要在 Awk 代码部分,增加形成新的代码的功能。

这是我的代码:

( onece_load_push () 
{
    be_loadpushed="$1" &&
    errtimes="${2:-0}" &&
    
    push_loaded_cmdmkr ()
    {
        awk '
        /Loaded image:/ {
            
            split($0, msgarr, "Loaded image: ") ;
            print "docker", "tag", "--", "'"'"'"msgarr[2]"'"'"'", "'"'$be_loadpushed'"'", "&&", "docker", "push", "--", "'"'$be_loadpushed'"'" }
        
        ' | tee -a /dev/stderr ;
    } &&
    
    xz -dc -- "$be_loadpushed".tar.xz |
        docker load -q |
        push_loaded_cmdmkr |
        
        bash &&
        { echo ok,$errtimes: "$be_loadpushed" >&2 ; } || 
        {
            echo err,$errtimes: "$be_loadpushed" >&2 ;
            exec bash -c "$(declare -f onece_load_push) ; onece_load_push '$be_loadpushed' $((errtimes+1))" ;
        } ;
} &&
(find * -name \*.tar.xz -type f | sed -e s/.tar.xz\$// -- -) |
    xargs -P0 -i -- bash -c "$(declare -f onece_load_push) ; onece_load_push '{}' " )

我 Awk 那部分代码怎么拼出来的?一点一点拼出来的。

不熟悉的时候可以每写一点就在前面加上个 echo 101.202.103.104/ahahaha/racket:7.9-cs | 执行来试试效果,熟了就能直接写了。

这样一来,你可以利用目录结构,把目标镜像的 全名 (指包括 作者名 镜像名 标签 的完整的镜像名)完全改成你想要的样子,然后用上面的脚本即可批量改名并上传( push )。

比如,你只是想把所有的 101.202.103.104 源改为 101.207.1.1 ,你就可以在当前目录执行这个:

mv 101.202.103.104 101.207.1.1

然后执行脚本。

别的改名需要也是以此类推!

备忘

(话说啊,我为啥这么循序渐进写下我命令形成的每一环节呢?因为 BASH 不适合开发(我是说真的),如果有错的话很可能你找到姥姥家去也很难找明白原因,有可能得把祖坟挖个遍,然后才发现哦是这里有错,然后你对 Unix 系统的认识也加深了,只不过更多的可能是你找不到。。。所以,我建议,命令越短越好,能别太复杂就别太复杂,所有的 DSL 我都是这么对待的😛😜🙃🙃。)


awsr
13 声望0 粉丝

« 上一篇
递归与奇迹