sed 回転コマンドの作成


まえがき

sedコマンドで回転コマンド作成した。そして今回はちょっと頑張ってunicodeのコードポイント指定して使うようなスクリプトにアレンジしてみた。

参考文献

環境

$sed --version
/usr/local/bin/sed (GNU sed) 4.7
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

作者 Jay Fenlason、 Tom Lord、 Ken Pizzini、
Paolo Bonzini、 Jim Meyering、および Assaf Gordon。
GNU sed home page: <https://www.gnu.org/software/sed/>.
General help using GNU software: <https://www.gnu.org/gethelp/>.
E-mail bug reports to: <[email protected]>.
$bash --version
GNU bash, バージョン 5.0.0(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
ライセンス GPLv3+: GNU GPL バージョン 3 またはそれ以降 <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

スクリプト

1.left_stair.sh

先頭要素取得して末尾要素に追加していくパターン。

先頭要素のunicodeコードポイント部分とそれ以外に分類して、先頭要素のunicodeコードポイント部分をそれ以外の部分の後ろにくっつける。終了条件に先頭要素のunicodeコードポイント部分が来たら、置換終了する。最初はそう考えていたけど、無限ループになってしまったから、headで対象要素数分上から抽出するようにハンドリングした。なぜ、終了条件にマッチしなかったのかまだよくわかっていない。

#!/bin/bash

usage(){
cat <<EOS
Ref:
https://ja.wikipedia.org/wiki/Unicode%E4%B8%80%E8%A6%A7_0000-0FFF
Usage:
\$$0 U30{4..4}{{1..9},{A..F}} U30{5..8}{{0..9},{A..F}} U30{9..9}{{0..6},{9..9},{A..F}}
\$$0 U30{A..F}{{0..9},{A..F}}
\$$0 U00{2..7}{{0..9},{A..E}}
\$$0 U30{3..4}{{0..9},{A..F}}
\$$0 U1f4a{{0..9},{A..F}}
\$$0 U1f4{a..b}{{0..9},{A..F}}
\$echo U1f4{a..b}{{0..9},{A..F}} | $0
\$$0 U{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}
\$$0 U{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}
\$$0 U{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}
\$$0 U{{0..9},{A..F}}{{0..9},{A..F}}
\$$0 U{{0..9},{A..F}}
EOS
  exit 1
}

chk_fmt(){
  [ -z "$@" ] && usage
  declare -a argv=()
  local argv=($@)
  local argc="${#argv[@]}"
  for ele in "${argv[@]}";do
    echo "${ele}" | grep -v "^U" 1>/dev/null 2>&1 && usage
    [ $(echo "${ele}" | xxd -ps -c1 | grep -v ^0a$ | grep -c "") -gt 9 ] && usage
  done
}

exec_zero_pad(){
  declare -a argv=()
  local argv=($@)
  local argc="${#argv[@]}"
  for ele in "${argv[@]}";do
    #debug用
    #echo "${ele}" $(zero_pad "${ele}")
    zero_pad "${ele}"
  done
}

zero_pad(){
  local mx_len=8
  local prefix=$(echo "$@" | awk '{print substr($1,1,1)}')
  local except_prefix=$(echo "$@" | awk '{print substr($1,2)}')
  local rsv_args_len=$(echo "${except_prefix}" | awk '{print length($1)}')
  local diff=$((mx_len-rsv_args_len))
  echo "" | awk -v pad="${diff}" -v prefix="${prefix}" -v except_prefix="${except_prefix}" '
  BEGIN{
    zero_str=""
  }
  {
    while(pad-- >0){
      zero_str=zero_str""sprintf("%s","0");
    }
    print prefix""zero_str""except_prefix
  }'
}

circle(){
  declare -a argv=()
  local argv=($@)
  local argc="${#argv[@]}"
  #debug用
  #echo "cnt:${argc},start:${argv[@]:0:1},end:${argv[@]:${#argv[@]}-1:1}"
  cmd=$(echo '\\\\\\\\'{${argv[@]}} | tr ' ' ',' | sed 's;^;echo ;')
  eval $cmd | tr -d ' ' | xargs -I@ bash -c 'echo @ | sed -n -r -e "
  p
  :loop
  s/(\\\U........){1}(.*)/\2\1/
  p
  /@/!bloop
  "
  ' |sed 's/^/echo -e \x27/;s/$/\x27/' | head -n$argc | sh
}

main(){
  local rsv_args="$(cat -)";
  chk_fmt "${rsv_args}"
  zero_pad_done=$(exec_zero_pad "${rsv_args}")
  circle $zero_pad_done
  exit 0
}

[ -p /dev/stdin ] && cat - | main
[ -p /dev/stdin ] || echo -ne "$@" | main

先頭要素が次の対象行の末尾要素に追加されていく様子がわかる。

$echo U1f4a{{0..9},{A..F}} | ./left_stair.sh
💠💡💢💣💤💥💦💧💨💩💪💫💬💭💮💯
💡💢💣💤💥💦💧💨💩💪💫💬💭💮💯💠
💢💣💤💥💦💧💨💩💪💫💬💭💮💯💠💡
💣💤💥💦💧💨💩💪💫💬💭💮💯💠💡💢
💤💥💦💧💨💩💪💫💬💭💮💯💠💡💢💣
💥💦💧💨💩💪💫💬💭💮💯💠💡💢💣💤
💦💧💨💩💪💫💬💭💮💯💠💡💢💣💤💥
💧💨💩💪💫💬💭💮💯💠💡💢💣💤💥💦
💨💩💪💫💬💭💮💯💠💡💢💣💤💥💦💧
💩💪💫💬💭💮💯💠💡💢💣💤💥💦💧💨
💪💫💬💭💮💯💠💡💢💣💤💥💦💧💨💩
💫💬💭💮💯💠💡💢💣💤💥💦💧💨💩💪
💬💭💮💯💠💡💢💣💤💥💦💧💨💩💪💫
💭💮💯💠💡💢💣💤💥💦💧💨💩💪💫💬
💮💯💠💡💢💣💤💥💦💧💨💩💪💫💬💭
💯💠💡💢💣💤💥💦💧💨💩💪💫💬💭💮

2.right_stair.sh

末尾要素取得して先頭要素に追加していくパターン。

末尾要素のunicodeコードポイント部分とそれ以外に分類して、末尾要素のunicodeコードポイント部分をそれ以外の部分の前にくっつける。終了条件に末尾要素のunicodeコードポイント部分が来たら、置換終了する。最初はそう考えていたけど、無限ループになってしまったから、headで対象要素数分上から抽出するようにハンドリングした。なぜ、終了条件にマッチしなかったのかまだよくわかっていない。left_stair.shと同じ。

#!/bin/bash

usage(){
cat <<EOS
Ref:
https://ja.wikipedia.org/wiki/Unicode%E4%B8%80%E8%A6%A7_0000-0FFF
Usage:
\$$0 U30{4..4}{{1..9},{A..F}} U30{5..8}{{0..9},{A..F}} U30{9..9}{{0..6},{9..9},{A..F}}
\$$0 U30{A..F}{{0..9},{A..F}}
\$$0 U00{2..7}{{0..9},{A..E}}
\$$0 U30{3..4}{{0..9},{A..F}}
\$$0 U1f4a{{0..9},{A..F}}
\$$0 U1f4{a..b}{{0..9},{A..F}}
\$echo U1f4{a..b}{{0..9},{A..F}} | $0
\$$0 U{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}
\$$0 U{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}
\$$0 U{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}
\$$0 U{{0..9},{A..F}}{{0..9},{A..F}}
\$$0 U{{0..9},{A..F}}
EOS
  exit 1
}

chk_fmt(){
  [ -z "$@" ] && usage
  declare -a argv=()
  local argv=($@)
  local argc="${#argv[@]}"
  for ele in "${argv[@]}";do
    echo "${ele}" | grep -v "^U" 1>/dev/null 2>&1 && usage
    [ $(echo "${ele}" | xxd -ps -c1 | grep -v ^0a$ | grep -c "") -gt 9 ] && usage
  done
}

exec_zero_pad(){
  declare -a argv=()
  local argv=($@)
  local argc="${#argv[@]}"
  for ele in "${argv[@]}";do
    #debug用
    #echo "${ele}" $(zero_pad "${ele}")
    zero_pad "${ele}"
  done
}

zero_pad(){
  local mx_len=8
  local prefix=$(echo "$@" | awk '{print substr($1,1,1)}')
  local except_prefix=$(echo "$@" | awk '{print substr($1,2)}')
  local rsv_args_len=$(echo "${except_prefix}" | awk '{print length($1)}')
  local diff=$((mx_len-rsv_args_len))
  echo "" | awk -v pad="${diff}" -v prefix="${prefix}" -v except_prefix="${except_prefix}" '
  BEGIN{
    zero_str=""
  }
  {
    while(pad-- >0){
      zero_str=zero_str""sprintf("%s","0");
    }
    print prefix""zero_str""except_prefix
  }'
}

circle(){
  declare -a argv=()
  local argv=($@)
  local argc="${#argv[@]}"
  #debug用
  #echo "cnt:${argc},start:${argv[@]:0:1},end:${argv[@]:${#argv[@]}-1:1}"
  cmd=$(echo '\\\\\\\\'{${argv[@]}} | tr ' ' ',' | sed 's;^;echo ;')
  eval $cmd | tr -d ' ' | xargs -I@ bash -c 'echo @ | sed -n -r -e "
  p
  :loop
  s/(.*)(\\\U........){1}/\2\1/
  p
  /@/!bloop
  "
  ' |sed 's/^/echo -e \x27/;s/$/\x27/' | head -n$argc | sh
}

main(){
  local rsv_args="$(cat -)";
  chk_fmt "${rsv_args}"
  zero_pad_done=$(exec_zero_pad "${rsv_args}")
  circle $zero_pad_done
  exit 0
}

[ -p /dev/stdin ] && cat - | main
[ -p /dev/stdin ] || echo -ne "$@" | main

末尾要素が先頭要素に追加されていく様子がわかる。

$echo U1f4a{{0..9},{A..F}} | ./right_stair.sh
💠💡💢💣💤💥💦💧💨💩💪💫💬💭💮💯
💯💠💡💢💣💤💥💦💧💨💩💪💫💬💭💮
💮💯💠💡💢💣💤💥💦💧💨💩💪💫💬💭
💭💮💯💠💡💢💣💤💥💦💧💨💩💪💫💬
💬💭💮💯💠💡💢💣💤💥💦💧💨💩💪💫
💫💬💭💮💯💠💡💢💣💤💥💦💧💨💩💪
💪💫💬💭💮💯💠💡💢💣💤💥💦💧💨💩
💩💪💫💬💭💮💯💠💡💢💣💤💥💦💧💨
💨💩💪💫💬💭💮💯💠💡💢💣💤💥💦💧
💧💨💩💪💫💬💭💮💯💠💡💢💣💤💥💦
💦💧💨💩💪💫💬💭💮💯💠💡💢💣💤💥
💥💦💧💨💩💪💫💬💭💮💯💠💡💢💣💤
💤💥💦💧💨💩💪💫💬💭💮💯💠💡💢💣
💣💤💥💦💧💨💩💪💫💬💭💮💯💠💡💢
💢💣💤💥💦💧💨💩💪💫💬💭💮💯💠💡
💡💢💣💤💥💦💧💨💩💪💫💬💭💮💯💠

3.square.sh

上記スクリプトをそれぞれ左と右、右と左に並べて、与える引数を正順と逆順の2つにしたパターン。

#!/bin/bash

usage(){
  cat <<EOF
Usage:
\$echo U1f4{a..b}{{0..9},{A..F}} | $0
\$$0 U30{4..4}{{1..9},{A..F}} U30{5..8}{{0..9},{A..F}} U30{9..9}{{0..6},{9..9},{A..F}}
\$$0 U30{A..F}{{0..9},{A..F}}
\$$0 U00{2..7}{{0..9},{A..E}}
\$$0 U30{3..4}{{0..9},{A..F}}
\$$0 U1f4a{{0..9},{A..F}}
\$$0 U1f4{a..b}{{0..9},{A..F}}
\$$0 U{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}
\$$0 U{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}
\$$0 U{{0..9},{A..F}}{{0..9},{A..F}}{{0..9},{A..F}}
\$$0 U{{0..9},{A..F}}{{0..9},{A..F}}
\$$0 U{{0..9},{A..F}}
EOF
}

chk_args(){
  declare -a argv=()
  local argv=($@)
  local argc="${#argv[@]}"
  [ "${argc}" -le 1 ] && usage && exit 1
}

circle(){
  paste -d' ' <(echo "$@" | ./left_stair.sh) <(echo "$@" | tr ' ' '\n' | tac | xargs | ./right_stair.sh)
  paste -d' ' <(echo "$@" | ./right_stair.sh) <(echo "$@" | tr ' ' '\n' | tac | xargs | ./left_stair.sh)
}

main(){
  local init_args="$(cat -)";
  chk_args "${init_args}"
  while read ln;do circle "${ln}";done <<<"${init_args}"
}

[ -p /dev/stdin ] && cat - | main
[ -p /dev/stdin ] || echo -ne "$@" | main

見慣れたやつ。とおもったけど、なんか違った。雰囲気同じ。revの挙動とは違うなー。難しい。

$echo U1f4a{{1..9},{A..F}} | ./square.sh 
💡💢💣💤💥💦💧💨💩💪💫💬💭💮💯 💯💮💭💬💫💪💩💨💧💦💥💤💣💢💡
💢💣💤💥💦💧💨💩💪💫💬💭💮💯💡 💡💯💮💭💬💫💪💩💨💧💦💥💤💣💢
💣💤💥💦💧💨💩💪💫💬💭💮💯💡💢 💢💡💯💮💭💬💫💪💩💨💧💦💥💤💣
💤💥💦💧💨💩💪💫💬💭💮💯💡💢💣 💣💢💡💯💮💭💬💫💪💩💨💧💦💥💤
💥💦💧💨💩💪💫💬💭💮💯💡💢💣💤 💤💣💢💡💯💮💭💬💫💪💩💨💧💦💥
💦💧💨💩💪💫💬💭💮💯💡💢💣💤💥 💥💤💣💢💡💯💮💭💬💫💪💩💨💧💦
💧💨💩💪💫💬💭💮💯💡💢💣💤💥💦 💦💥💤💣💢💡💯💮💭💬💫💪💩💨💧
💨💩💪💫💬💭💮💯💡💢💣💤💥💦💧 💧💦💥💤💣💢💡💯💮💭💬💫💪💩💨
💩💪💫💬💭💮💯💡💢💣💤💥💦💧💨 💨💧💦💥💤💣💢💡💯💮💭💬💫💪💩
💪💫💬💭💮💯💡💢💣💤💥💦💧💨💩 💩💨💧💦💥💤💣💢💡💯💮💭💬💫💪
💫💬💭💮💯💡💢💣💤💥💦💧💨💩💪 💪💩💨💧💦💥💤💣💢💡💯💮💭💬💫
💬💭💮💯💡💢💣💤💥💦💧💨💩💪💫 💫💪💩💨💧💦💥💤💣💢💡💯💮💭💬
💭💮💯💡💢💣💤💥💦💧💨💩💪💫💬 💬💫💪💩💨💧💦💥💤💣💢💡💯💮💭
💮💯💡💢💣💤💥💦💧💨💩💪💫💬💭 💭💬💫💪💩💨💧💦💥💤💣💢💡💯💮
💯💡💢💣💤💥💦💧💨💩💪💫💬💭💮 💮💭💬💫💪💩💨💧💦💥💤💣💢💡💯
💡💢💣💤💥💦💧💨💩💪💫💬💭💮💯 💯💮💭💬💫💪💩💨💧💦💥💤💣💢💡
💯💡💢💣💤💥💦💧💨💩💪💫💬💭💮 💮💭💬💫💪💩💨💧💦💥💤💣💢💡💯
💮💯💡💢💣💤💥💦💧💨💩💪💫💬💭 💭💬💫💪💩💨💧💦💥💤💣💢💡💯💮
💭💮💯💡💢💣💤💥💦💧💨💩💪💫💬 💬💫💪💩💨💧💦💥💤💣💢💡💯💮💭
💬💭💮💯💡💢💣💤💥💦💧💨💩💪💫 💫💪💩💨💧💦💥💤💣💢💡💯💮💭💬
💫💬💭💮💯💡💢💣💤💥💦💧💨💩💪 💪💩💨💧💦💥💤💣💢💡💯💮💭💬💫
💪💫💬💭💮💯💡💢💣💤💥💦💧💨💩 💩💨💧💦💥💤💣💢💡💯💮💭💬💫💪
💩💪💫💬💭💮💯💡💢💣💤💥💦💧💨 💨💧💦💥💤💣💢💡💯💮💭💬💫💪💩
💨💩💪💫💬💭💮💯💡💢💣💤💥💦💧 💧💦💥💤💣💢💡💯💮💭💬💫💪💩💨
💧💨💩💪💫💬💭💮💯💡💢💣💤💥💦 💦💥💤💣💢💡💯💮💭💬💫💪💩💨💧
💦💧💨💩💪💫💬💭💮💯💡💢💣💤💥 💥💤💣💢💡💯💮💭💬💫💪💩💨💧💦
💥💦💧💨💩💪💫💬💭💮💯💡💢💣💤 💤💣💢💡💯💮💭💬💫💪💩💨💧💦💥
💤💥💦💧💨💩💪💫💬💭💮💯💡💢💣 💣💢💡💯💮💭💬💫💪💩💨💧💦💥💤
💣💤💥💦💧💨💩💪💫💬💭💮💯💡💢 💢💡💯💮💭💬💫💪💩💨💧💦💥💤💣
💢💣💤💥💦💧💨💩💪💫💬💭💮💯💡 💡💯💮💭💬💫💪💩💨💧💦💥💤💣💢

あとがき

絵文字楽しい!以上、ありがとうございました。

気になったところ

headコマンドによるハンドリング。簡単な例で比較。

終了条件にマッチする。

$cat ./aa.sh
#!/bin/bash

echo {a..j} | tr -d ' ' | xargs -I@ bash -c 'echo @ | sed -n -r "
:loop
s/(.){1}(.*)/\2\1/
p
/@/!bloop
"
'
$echo {a..j} | tr -d ' ' | xargs -I@ bash -c 'echo @ | sed -n -r "
:loop
s/(.){1}(.*)/\2\1/
p
/@/!bloop
"
'
bcdefghija
cdefghijab
defghijabc
efghijabcd
fghijabcde
ghijabcdef
hijabcdefg
ijabcdefgh
jabcdefghi
abcdefghij

終了条件にマッチする。

echo ありがとうございました | xargs -I@ bash -c 'echo @ | sed -n -r "
:loop
s/(.){1}(.*)/\2\1/
p
/@/!bloop
"
'
$echo ありがとうございました | xargs -I@ bash -c 'echo @ | sed -n -r "
:loop
s/(.){1}(.*)/\2\1/
p
/@/!bloop
"
'
りがとうございましたあ
がとうございましたあり
とうございましたありが
うございましたありがと
ございましたありがとう
ざいましたありがとうご
いましたありがとうござ
ましたありがとうござい
したありがとうございま
たありがとうございまし
ありがとうございました

終了条件にマッチしない。無限ループ。headでハンドリング。

echo '\\\\U1f4a'{0..2} | tr -d ' ' | xargs -I@ bash -c 'echo @ | sed -n -r -e "
:loop
s/(\\\U.....){1}(.*)/\2\1/
p
/@/!bloop
"
' | head -n4 

デバッグで確認して、マッチしそうなんだけどなー。次のループが始まっている。

$echo '\\\\U1f4a'{0..2} | tr -d ' ' | xargs -I@ bash -c 'echo @ | sed  --debug -n -r -e "
:loop
s/(\\\U.....){1}(.*)/\2\1/
p
/@/!bloop
"
' > looop
^C
$sed -n '1,39p' looop
SED PROGRAM:
  :loop
  s/(\\\\U.....){1}(.*)/\2\1/
  p
  /\\U1f4a0\\U1f4a1\\U1f4a2/! b loop
INPUT:   'STDIN' line 1
PATTERN: \\U1f4a0\\U1f4a1\\U1f4a2
COMMAND: :loop
COMMAND: s/(\\\\U.....){1}(.*)/\2\1/
MATCHED REGEX REGISTERS
  regex[0] = 0-21 '\U1f4a0\U1f4a1\U1f4a2'
  regex[1] = 0-7 '\U1f4a0'
  regex[2] = 7-21 '\U1f4a1\U1f4a2'
PATTERN: \\U1f4a1\\U1f4a2\\U1f4a0
COMMAND: p
\U1f4a1\U1f4a2\U1f4a0
COMMAND: /\\U1f4a0\\U1f4a1\\U1f4a2/! b loop
COMMAND: :loop
COMMAND: s/(\\\\U.....){1}(.*)/\2\1/
MATCHED REGEX REGISTERS
  regex[0] = 0-21 '\U1f4a1\U1f4a2\U1f4a0'
  regex[1] = 0-7 '\U1f4a1'
  regex[2] = 7-21 '\U1f4a2\U1f4a0'
PATTERN: \\U1f4a2\\U1f4a0\\U1f4a1
COMMAND: p
\U1f4a2\U1f4a0\U1f4a1
COMMAND: /\\U1f4a0\\U1f4a1\\U1f4a2/! b loop
COMMAND: :loop
COMMAND: s/(\\\\U.....){1}(.*)/\2\1/
MATCHED REGEX REGISTERS
  regex[0] = 0-21 '\U1f4a2\U1f4a0\U1f4a1'
  regex[1] = 0-7 '\U1f4a2'
  regex[2] = 7-21 '\U1f4a0\U1f4a1'
PATTERN: \\U1f4a0\\U1f4a1\\U1f4a2
COMMAND: p
\U1f4a0\U1f4a1\U1f4a2
COMMAND: /\\U1f4a0\\U1f4a1\\U1f4a2/! b loop
COMMAND: :loop
COMMAND: s/(\\\\U.....){1}(.*)/\2\1/

バックスラッシュへのマッチが難しいのかな。

マッチしそうだと思ったけど。むずい。

PATTERN: \\U1f4a0\\U1f4a1\\U1f4a2

COMMAND: /\\U1f4a0\\U1f4a1\\U1f4a2/! b loop