history.sh (80669B)
1 #!/bin/bash 2 3 ## @bleopt history_limit_length 4 ## 履歴に登録するコマンドの最大文字数を指定します。 5 ## この値を超える長さのコマンドは履歴に登録されません。 6 bleopt/declare -v history_limit_length 10000 7 8 #============================================================================== 9 # ble/history:bash @history.bash 10 11 ## @arr _ble_history 12 ## コマンド履歴項目を保持する。 13 ## 14 ## @arr _ble_history_edit 15 ## @arr _ble_history_dirt 16 ## _ble_history_edit 編集されたコマンド履歴項目を保持する。 17 ## _ble_history の各項目と対応し、必ず同じ数・添字の要素を持つ。 18 ## _ble_history_dirt は編集されたかどうかを保持する。 19 ## _ble_history の各項目と対応し、変更のあったい要素にのみ値 1 を持つ。 20 ## 21 ## @var _ble_history_index 22 ## 現在の履歴項目の番号 23 ## 24 _ble_history=() 25 _ble_history_edit=() 26 _ble_history_dirt=() 27 _ble_history_index=0 28 29 ## @var _ble_history_count 30 ## 現在の履歴項目の総数 31 ## 32 ## これらの変数はコマンド履歴を対象としているときにのみ用いる。 33 ## 34 _ble_history_count= 35 36 function ble/builtin/history/is-empty { 37 # Note #D1629: 以前の実装 (#D1120) では ! builtin history -p '!!' を使ってい 38 # たが、状況によって history -p で履歴項目が減少するのでサブシェルの中で評 39 # 価する必要がある。サブシェルの中に既にいる時にはこの fork は省略できると 40 # 考えていたが、サブシェルの中にいる場合でも後で履歴を使う為に履歴項目が変 41 # 化すると困るので、結局この手法だと常にサブシェルを起動する必要がある。代 42 # わりに history 1 の出力を確認する実装に変更する事にした。 43 ! ble/util/assign.has-output 'builtin history 1' 44 } 45 46 ## @fn ble/builtin/history/.check-timestamp-sigsegv status 47 ## #D1831: Bash 4.4 以下では履歴ファイル (HISTFILE) に不正な timestamp 48 ## (0x7FFFFFFF+1900年より後を指す巨大な unix time) が含まれていると segfault 49 ## する。実際に SIGSEGV で終了した時に履歴ファイルを確認して問題の行番号を出 50 ## 力する。 51 if ((_ble_bash>=50000)); then 52 function ble/builtin/history/.check-timestamp-sigsegv { :; } 53 else 54 function ble/builtin/history/.check-timestamp-sigsegv { 55 local stat=$1 56 ((stat)) || return 0 57 58 local ret=11 59 ble/builtin/trap/sig#resolve SIGSEGV 60 ((stat==128+ret)) || return 0 61 62 local msg="bash: SIGSEGV: suspicious timestamp in HISTFILE" 63 64 local histfile=${HISTFILE-} 65 if [[ -s $histfile ]]; then 66 msg="$msg='$histfile'" 67 local rex_broken_timestamp='^#[0-9]\{12\}' 68 ble/util/assign line 'ble/bin/grep -an "$rex_broken_timestamp" "$histfile"' 69 ble/string#split line : "$line" 70 [[ $line =~ ^[0-9]+$ ]] && msg="$msg (line $line)" 71 fi 72 73 if [[ ${_ble_edit_io_fname2-} ]]; then 74 ble/util/print $'\n'"$msg" >> "$_ble_edit_io_fname2" 75 else 76 ble/util/print "$msg" >&2 77 fi 78 } 79 fi 80 81 ## @fn ble/builtin/history/.dump args... 82 ## #D1831: timestamp に不正な値が含まれていた時のメッセージを検出する為、一時 83 ## 的に LC_MESSAGES を設定して builtin history を呼び出します。更にこの状況で、 84 ## bash-3.2 以下で無限ループになる問題を回避する為に、bash-3.2 以下では 85 ## conditional-sync 経由で呼び出します。 86 if ((_ble_bash<40000)); then 87 # Note (#D1831): bash-3.2 以下では不正な timestamp が history に含まれている 88 # と無限ループになるので timeout=3000 で強制終了する。然し、実際に確認して 89 # みると、conditional-sync 経由で呼び出した時には無限ループにならずに 90 # timeout する前に SIGSEGV になる様である 91 function ble/builtin/history/.dump.proc { 92 local LC_ALL= LC_MESSAGES=C 2>/dev/null 93 builtin history "${args[@]}" 94 ble/util/unlocal LC_ALL LC_MESSAGES 2>/dev/null 95 } 96 function ble/builtin/history/.dump { 97 local -a args; args=("$@") 98 ble/util/conditional-sync \ 99 ble/builtin/history/.dump.proc \ 100 true 100 progressive-weight:timeout=3000:SIGKILL 101 local ext=$? 102 if ((ext==142)); then 103 printf 'ble.sh: timeout: builtin history %s' "$*" >&"$_ble_util_fd_stderr" 104 local ret=11 105 ble/builtin/trap/sig#resolve SIGSEGV 106 ((ext=128+ret)) 107 fi 108 ble/builtin/history/.check-timestamp-sigsegv "$ext" 109 return "$ext" 110 } 111 else 112 function ble/builtin/history/.dump { 113 local LC_ALL= LC_MESSAGES=C 2>/dev/null 114 builtin history "$@" 115 ble/util/unlocal LC_ALL LC_MESSAGES 2>/dev/null 116 } 117 fi 118 119 if ((_ble_bash<40000)); then 120 function ble/builtin/history/.get-min { 121 ble/util/assign-words min 'ble/builtin/history/.dump | head -1' 122 min=${min/'*'} 123 } 124 else 125 function ble/builtin/history/.get-min { 126 ble/util/assign-words min 'builtin history | head -1' 127 min=${min/'*'} 128 } 129 fi 130 function ble/builtin/history/.get-max { 131 ble/util/assign-words max 'builtin history 1' 132 max=${max/'*'} 133 } 134 135 #------------------------------------------------------------------------------ 136 # initialize _ble_history @history.bash.load 137 138 ## @var _ble_history_load_done 139 _ble_history_load_done= 140 141 # @hook history_reset_background (defined in def.sh) 142 function ble/history:bash/clear-background-load { 143 blehook/invoke history_reset_background 144 } 145 146 ## @fn ble/history:bash/load 147 if ((_ble_bash>=40000)); then 148 # _ble_bash>=40000 で利用できる以下の機能に依存する 149 # ble/util/is-stdin-ready (via ble/util/idle/IS_IDLE) 150 # ble/util/mapfile 151 152 _ble_history_load_resume=0 153 _ble_history_load_bgpid= 154 155 # history > tmp 156 ## @fn ble/history:bash/load/.background-initialize 157 ## @var[in] arg_count 158 ## @var[in] load_strategy 159 function ble/history:bash/load/.background-initialize { 160 if ble/builtin/history/is-empty; then 161 # Note: rcfile から呼び出すと history が未ロードなのでロードする。 162 # 163 # Note: 当初は親プロセスで history -n にした方が二度手間にならず効率的と考えたが 164 # 以下の様な問題が生じたので、やはりサブシェルの中で history -n する事にした。 165 # 166 # 問題1: bashrc の謎の遅延 (memo.txt#D0702) 167 # shopt -s histappend の状態で親シェルで history -n を呼び出すと、 168 # bashrc を抜けてから Bash 本体によるプロンプトが表示されて、 169 # 入力を受け付けられる様になる迄に、謎の遅延が発生する。 170 # 特に履歴項目の数が HISTSIZE の丁度半分より多い時に起こる様である。 171 # 172 # history -n を呼び出す瞬間だけ shopt -u histappend して 173 # 直後に shopt -s histappend とすると、遅延は解消するが、 174 # 実際の動作を観察すると histappend が無効になってしまっている。 175 # 176 # 対策として、一時的に HISTSIZE を大きくして bashrc を抜けて、 177 # 最初のユーザからの入力の時に HISTSIZE を復元する事にした。 178 # これで遅延は解消できる様である。 179 # 180 # 問題2: 履歴の数が倍加する問題 (memo.txt#D0732) 181 # 親シェルで history -n を実行すると、 182 # shopt -s histappend の状態だと履歴項目の数が2倍になってしまう。 183 # bashrc を抜ける直前から最初にユーザの入力を受けるまでに倍加する。 184 # bashrc から抜けた後に Readline が独自に履歴を読み取るのだろう。 185 # 一方で shopt -u histappend の状態だとシェルが動作している内は問題ないが、 186 # シェルを終了した時に2倍に .bash_history の内容が倍になってしまう。 187 # 188 # これの解決方法は不明。(HISTFILE 等を弄ったりすれば可能かもれないが試していない) 189 # 190 builtin history -n 191 fi 192 local HISTTIMEFORMAT=__ble_ext__ 193 local -x INDEX_FILE=$history_indfile 194 195 local -x opt_source= opt_null= 196 if [[ $load_strategy == source ]]; then 197 opt_source=1 198 elif [[ $load_strategy == mapfile ]]; then 199 opt_null=1 200 fi 201 202 # from ble/util/writearray 203 if [[ ! $_ble_util_writearray_rawbytes ]]; then 204 local IFS=$_ble_term_IFS __ble_tmp; __ble_tmp=('\'{2,3}{0..7}{0..7}) 205 builtin eval "local _ble_util_writearray_rawbytes=\$'${__ble_tmp[*]}'" 206 fi 207 local -x __ble_rawbytes=$_ble_util_writearray_rawbytes # used by _ble_bin_awk_libES 208 local -x fname_stderr=${_ble_edit_io_fname2:-} 209 210 local apos=\' 211 # 482ms for 37002 entries 212 ble/builtin/history/.dump ${arg_count:+"$arg_count"} | ble/bin/awk -v apos="$apos" -v arg_offset="$arg_offset" -v _ble_bash="$_ble_bash" ' 213 '"$_ble_bin_awk_libES"' 214 215 BEGIN { 216 es_initialize(); 217 218 INDEX_FILE = ENVIRON["INDEX_FILE"]; 219 opt_null = ENVIRON["opt_null"]; 220 opt_source = ENVIRON["opt_source"]; 221 if (!opt_null && !opt_source) 222 printf("") > INDEX_FILE; # create file 223 224 fname_stderr = ENVIRON["fname_stderr"]; 225 fname_stderr_count = 0; 226 227 n = 0; 228 hindex = arg_offset; 229 } 230 231 function flush_line() { 232 if (n < 1) return; 233 234 if (opt_null) { 235 if (t ~ /^eval -- \$'"$apos"'([^'"$apos"'\\]|\\.)*'"$apos"'$/) 236 t = es_unescape(substr(t, 11, length(t) - 11)); 237 printf("%s%c", t, 0); 238 239 } else if (opt_source) { 240 if (t ~ /^eval -- \$'"$apos"'([^'"$apos"'\\]|\\.)*'"$apos"'$/) 241 t = es_unescape(substr(t, 11, length(t) - 11)); 242 gsub(/'"$apos"'/, "'"$apos"'\\'"$apos$apos"'", t); 243 print "_ble_history[" hindex "]=" apos t apos; 244 245 } else { 246 if (n == 1) { 247 if (t ~ /^eval -- \$'"$apos"'([^'"$apos"'\\]|\\.)*'"$apos"'$/) 248 print hindex > INDEX_FILE; 249 } else { 250 gsub(/['"$apos"'\\]/, "\\\\&", t); 251 gsub(/\n/, "\\n", t); 252 print hindex > INDEX_FILE; 253 t = "eval -- $" apos t apos; 254 } 255 print t; 256 } 257 258 hindex++; 259 n = 0; 260 t = ""; 261 } 262 263 function check_invalid_timestamp(line) { 264 if (line ~ /^ *[0-9]+\*? +.+: invalid timestamp/ && fname_stderr != "") { 265 sub(/^ *0*/, "bash: history !", line); 266 sub(/: invalid timestamp.*$/, ": invalid timestamp", line); 267 if (fname_stderr_count++ == 0) 268 print "" >> fname_stderr; 269 print line >> fname_stderr; 270 } 271 } 272 273 { 274 # Note: In Bash 5.0+, the error message of "invalid timestamp" 275 # goes into "stdout" instead of "stderr". 276 check_invalid_timestamp($0); 277 if (sub(/^ *[0-9]+\*? +(__ble_ext__|\?\?|.+: invalid timestamp)/, "", $0)) 278 flush_line(); 279 t = ++n == 1 ? $0 : t "\n" $0; 280 } 281 282 END { flush_line(); } 283 ' >| "$history_tmpfile.part" 284 ble/builtin/history/.check-timestamp-sigsegv "${PIPESTATUS[0]}" 285 ble/bin/mv -f "$history_tmpfile.part" "$history_tmpfile" 286 } 287 288 ## @fn ble/history:bash/load opts 289 ## @param[in] opts 290 ## async 291 ## 非同期で読み取ります。 292 ## append 293 ## 現在読み込み済みの履歴情報に追加します。 294 ## count=NUMBER 295 ## 最近の NUMBER 項目だけ読み取ります。 296 function ble/history:bash/load { 297 local opts=$1 298 local opt_async=; [[ :$opts: == *:async:* ]] && opt_async=1 299 300 local load_strategy=mapfile 301 if [[ $OSTYPE == cygwin* || $OSTYPE == msys* ]]; then 302 load_strategy=source 303 elif ((_ble_bash<50200)); then 304 load_strategy=nlfix 305 fi 306 307 local arg_count= arg_offset=0 308 [[ :$opts: == *:append:* ]] && 309 arg_offset=${#_ble_history[@]} 310 local rex=':count=([0-9]+):'; [[ :$opts: =~ $rex ]] && arg_count=${BASH_REMATCH[1]} 311 312 local history_tmpfile=$_ble_base_run/$$.history.load 313 local history_indfile=$_ble_base_run/$$.history.multiline-index 314 [[ $opt_async || :$opts: == *:init:* ]] || _ble_history_load_resume=0 315 316 [[ ! $opt_async ]] && ((_ble_history_load_resume<6)) && 317 blehook/invoke history_message "loading history ..." 318 while :; do 319 case $_ble_history_load_resume in 320 321 # 42ms 履歴の読み込み 322 (0) # 履歴ファイル生成を Background で開始 323 if [[ $_ble_history_load_bgpid ]]; then 324 builtin kill -9 "$_ble_history_load_bgpid" &>/dev/null 325 _ble_history_load_bgpid= 326 fi 327 328 : >| "$history_tmpfile" 329 if [[ $opt_async ]]; then 330 _ble_history_load_bgpid=$(ble/util/nohup 'ble/history:bash/load/.background-initialize' print-bgpid) 331 332 function ble/history:bash/load/.background-initialize-completed { 333 local history_tmpfile=$_ble_base_run/$$.history.load 334 [[ -s $history_tmpfile ]] || ! builtin kill -0 "$_ble_history_load_bgpid" 335 } &>/dev/null 336 337 ((_ble_history_load_resume++)) 338 else 339 ble/history:bash/load/.background-initialize 340 ((_ble_history_load_resume+=3)) 341 fi ;; 342 343 # 515ms ble/history:bash/load/.background-initialize 待機 344 (1) if [[ $opt_async ]] && ble/util/is-running-in-idle; then 345 ble/util/idle.wait-condition ble/history:bash/load/.background-initialize-completed 346 ((_ble_history_load_resume++)) 347 return 147 348 fi 349 ((_ble_history_load_resume++)) ;; 350 351 # Note: async でバックグラウンドプロセスを起動した後に、直接 (sync で) 352 # 呼び出された時、未だ処理が完了していなくても次のステップに進んでしまうので、 353 # 此処で条件が満たされるのを待つ (#D0745) 354 (2) while ! ble/history:bash/load/.background-initialize-completed; do 355 ble/util/msleep 50 356 [[ $opt_async ]] && ! ble/util/idle/IS_IDLE && return 148 357 done 358 ((_ble_history_load_resume++)) ;; 359 360 # 47ms _ble_history 初期化 (37000項目) 361 (3) _ble_history_load_bgpid= 362 ((arg_offset==0)) && _ble_history=() 363 if [[ $load_strategy == source ]]; then 364 # Cygwin #D0701 #D1605 365 # 620ms 99000項目 @ #D0701 366 source "$history_tmpfile" 367 elif [[ $load_strategy == nlfix ]]; then 368 builtin mapfile -O "$arg_offset" -t _ble_history < "$history_tmpfile" 369 else 370 builtin mapfile -O "$arg_offset" -t -d '' _ble_history < "$history_tmpfile" 371 fi 372 ble/builtin/history/erasedups/update-base 373 ((_ble_history_load_resume++)) ;; 374 375 # 47ms _ble_history_edit 初期化 (37000項目) 376 (4) ((arg_offset==0)) && _ble_history_edit=() 377 if [[ $load_strategy == source ]]; then 378 # 504ms Cygwin (99000項目) 379 _ble_history_edit=("${_ble_history[@]}") 380 elif [[ $load_strategy == nlfix ]]; then 381 builtin mapfile -O "$arg_offset" -t _ble_history_edit < "$history_tmpfile" 382 else 383 builtin mapfile -O "$arg_offset" -t -d '' _ble_history_edit < "$history_tmpfile" 384 fi 385 : >| "$history_tmpfile" 386 387 if [[ $load_strategy != nlfix ]]; then 388 ((_ble_history_load_resume+=3)) 389 continue 390 else 391 ((_ble_history_load_resume++)) 392 fi ;; 393 394 # 11ms 複数行履歴修正 (107/37000項目) 395 (5) local -a indices_to_fix 396 ble/util/mapfile indices_to_fix < "$history_indfile" 397 local i rex='^eval -- \$'\''([^\'\'']|\\.)*'\''$' 398 for i in "${indices_to_fix[@]}"; do 399 [[ ${_ble_history[i]} =~ $rex ]] && 400 builtin eval "_ble_history[i]=${_ble_history[i]:8}" 401 done 402 ((_ble_history_load_resume++)) ;; 403 404 # 11ms 複数行履歴修正 (107/37000項目) 405 (6) local -a indices_to_fix 406 [[ ${indices_to_fix+set} ]] || 407 ble/util/mapfile indices_to_fix < "$history_indfile" 408 local i 409 for i in "${indices_to_fix[@]}"; do 410 [[ ${_ble_history_edit[i]} =~ $rex ]] && 411 builtin eval "_ble_history_edit[i]=${_ble_history_edit[i]:8}" 412 done 413 ((_ble_history_load_resume++)) ;; 414 415 (7) [[ $opt_async ]] || blehook/invoke history_message 416 ((_ble_history_load_resume++)) 417 return 0 ;; 418 419 (*) return 1 ;; 420 esac 421 422 [[ $opt_async ]] && ! ble/util/idle/IS_IDLE && return 148 423 done 424 } 425 blehook history_reset_background!=_ble_history_load_resume=0 426 else 427 function ble/history:bash/load/.generate-source { 428 if ble/builtin/history/is-empty; then 429 # rcfile として起動すると history が未だロードされていない。 430 builtin history -n 431 fi 432 local HISTTIMEFORMAT=__ble_ext__ 433 434 # 285ms for 16437 entries 435 local apos="'" 436 ble/builtin/history/.dump ${arg_count:+"$arg_count"} | ble/bin/awk -v apos="'" ' 437 BEGIN { n = ""; } 438 439 #% # 何故かタイムスタンプがコマンドとして読み込まれてしまう 440 /^ *[0-9]+\*? +(__ble_ext__|\?\?)#[0-9]/ { next; } 441 442 #% # ※rcfile として読み込むと HISTTIMEFORMAT が ?? に化ける。 443 /^ *[0-9]+\*? +(__ble_ext__|\?\?|.+: invalid timestamp)/ { 444 if (n != "") { 445 n = ""; 446 print " " apos t apos; 447 } 448 449 n = $1; t = ""; 450 sub(/^ *[0-9]+\*? +(__ble_ext__|\?\?|.+: invalid timestamp)/, "", $0); 451 } 452 { 453 line = $0; 454 if (line ~ /^eval -- \$'"$apos"'([^'"$apos"'\\]|\\.)*'"$apos"'$/) 455 line = apos substr(line, 9) apos; 456 else 457 gsub(apos, apos "\\" apos apos, line); 458 459 #% # 対策 #D1241: bash-3.2 以前では ^A, ^? が ^A^A, ^A^? に化ける 460 gsub(/\001/, "'"$apos"'${_ble_term_SOH}'"$apos"'", line); 461 gsub(/\177/, "'"$apos"'${_ble_term_DEL}'"$apos"'", line); 462 463 #% # 対策 #D1270: MSYS2 で ^M を代入すると消える 464 gsub(/\015/, "'"$apos"'${_ble_term_CR}'"$apos"'", line); 465 466 t = t != "" ? t "\n" line : line; 467 } 468 END { 469 if (n != "") { 470 n = ""; 471 print " " apos t apos; 472 } 473 } 474 ' 475 } 476 477 function ble/history:bash/load { 478 local opts=$1 479 local opt_append= 480 [[ :$opts: == *:append:* ]] && opt_append=1 481 482 local arg_count= rex=':count=([0-9]+):' 483 [[ :$opts: =~ $rex ]] && arg_count=${BASH_REMATCH[1]} 484 485 blehook/invoke history_message "loading history..." 486 487 # * プロセス置換にしてもファイルに書き出しても大した違いはない。 488 # 270ms for 16437 entries (generate-source の時間は除く) 489 # * プロセス置換×source は bash-3 で動かない。eval に変更する。 490 local result=$(ble/history:bash/load/.generate-source) 491 local IFS=$_ble_term_IFS 492 if [[ $opt_append ]]; then 493 if ((_ble_bash>=30100)); then 494 builtin eval -- "_ble_history+=($result)" 495 builtin eval -- "_ble_history_edit+=($result)" 496 else 497 local -a A; builtin eval -- "A=($result)" 498 _ble_history=("${_ble_history[@]}" "${A[@]}") 499 _ble_history_edit=("${_ble_history[@]}" "${A[@]}") 500 fi 501 else 502 builtin eval -- "_ble_history=($result)" 503 _ble_history_edit=("${_ble_history[@]}") 504 fi 505 ble/builtin/history/erasedups/update-base 506 ble/util/unlocal IFS 507 508 blehook/invoke history_message 509 } 510 fi 511 512 function ble/history:bash/initialize { 513 [[ $_ble_history_load_done ]] && return 0 514 ble/history:bash/load "init:$@"; local ext=$? 515 ((ext)) && return "$ext" 516 517 local old_count=$_ble_history_count new_count=${#_ble_history[@]} 518 _ble_history_load_done=1 519 _ble_history_count=$new_count 520 _ble_history_index=$_ble_history_count 521 ble/history/.update-position 522 523 # Note: 追加読み込みをした際に対応するデータを shift (history_share) 524 local delta=$((new_count-old_count)) 525 ((delta>0)) && blehook/invoke history_change insert "$old_count" "$delta" 526 } 527 528 #------------------------------------------------------------------------------ 529 # Bash history resolve-multiline @history.bash.mlfix 530 531 if ((_ble_bash>=30100)); then 532 # Note: Bash 3.0 では history -s がまともに動かないので 533 # 複数行の履歴項目を builtin history に追加する方法が今の所不明である。 534 535 _ble_history_mlfix_done= 536 _ble_history_mlfix_resume=0 537 _ble_history_mlfix_bgpid= 538 539 ## @fn ble/history:bash/resolve-multiline/.awk reason 540 ## 541 ## @param[in] reason 542 ## 呼び出しの用途を指定する文字列です。 543 ## 544 ## resolve ... 初期化時の history 再構築 545 ## history コマンドの出力形式で標準入力を解析します。 546 ## 各行は '番号 HISTTIMEFORMATコマンド' の形式をしている。 547 ## 548 ## read ... history -r によるファイルからの読み出し 549 ## 履歴ファイルの形式で標準入力を解析します。 550 ## 各行は '#%s' または 'コマンド' の形式をしている。 551 ## ble.sh では先頭行が '#%s' の時の複数行モードには対応しない。 552 ## 553 ## @var[in] tmpfile_base 554 function ble/history:bash/resolve-multiline/.awk { 555 if ((_ble_bash>=50000)); then 556 local -x epoch=$EPOCHSECONDS 557 elif ((_ble_bash>=40400)); then 558 local -x epoch 559 ble/util/strftime -v epoch %s 560 fi 561 562 local -x reason=$1 563 local apos=\' 564 ble/bin/awk -v apos="$apos" -v _ble_bash="$_ble_bash" ' 565 BEGIN { 566 q = apos; 567 Q = apos "\\" apos apos; 568 reason = ENVIRON["reason"]; 569 is_resolve = reason == "resolve"; 570 571 TMPBASE = ENVIRON["tmpfile_base"]; 572 filename_source = TMPBASE ".part"; 573 if (is_resolve) 574 print "builtin history -c" > filename_source 575 576 entry_nline = 0; 577 entry_text = ""; 578 entry_time = ""; 579 if (_ble_bash >= 40400) 580 entry_time = ENVIRON["epoch"]; 581 582 command_count = 0; 583 584 multiline_count = 0; 585 modification_count = 0; 586 read_section_count = 0; 587 } 588 589 function write_flush(_, i, filename_section, t, c) { 590 if (command_count == 0) return; 591 if (command_count >= 2 || entry_time) { 592 filename_section = TMPBASE "." read_section_count++ ".part"; 593 for (i = 0; i < command_count; i++) { 594 t = command_time[i]; 595 c = command_text[i]; 596 if (t) print "#" t > filename_section; 597 print c > filename_section; 598 } 599 #% # Note: HISTTIMEFORMAT を指定するのは bash-4.4 で複数行読み取りを有効にする為。 600 print "HISTTIMEFORMAT=%s builtin history -r " filename_section > filename_source; 601 } else { 602 for (i = 0; i < command_count; i++) { 603 c = command_text[i]; 604 gsub(/'"$apos"'/, Q, c); 605 print "builtin history -s -- " q c q > filename_source; 606 } 607 } 608 command_count = 0; 609 } 610 function write_complex(value) { 611 write_flush(); 612 print "builtin history -s -- " value > filename_source; 613 } 614 615 function register_command(cmd) { 616 command_time[command_count] = entry_time; 617 command_text[command_count] = cmd; 618 command_count++; 619 } 620 621 function is_escaped_command(cmd) { 622 return cmd ~ /^eval -- \$'"$apos"'([^'"$apos"'\\]|\\[\\'"$apos"'nt])*'"$apos"'$/; 623 } 624 function unescape_command(cmd) { 625 cmd = substr(cmd, 11, length(cmd) - 11); 626 gsub(/\\\\/, "\\q", cmd); 627 gsub(/\\n/, "\n", cmd); 628 gsub(/\\t/, "\t", cmd); 629 gsub(/\\'"$apos"'/, "'"$apos"'", cmd); 630 gsub(/\\q/, "\\", cmd); 631 return cmd; 632 } 633 function register_escaped_command(cmd) { 634 multiline_count++; 635 modification_count++; 636 if (_ble_bash >= 40400) { 637 register_command(unescape_command(cmd)); 638 } else { 639 write_complex(substr(cmd, 9)); 640 } 641 } 642 643 function register_multiline_command(cmd) { 644 multiline_count++; 645 if (_ble_bash >= 40040) { 646 register_command(cmd); 647 } else { 648 gsub(/'"$apos"'/, Q, cmd); 649 write_complex(q cmd q); 650 } 651 } 652 653 function flush_entry() { 654 if (entry_nline < 1) return; 655 656 if (is_escaped_command(entry_text)) { 657 register_escaped_command(entry_text) 658 } else if (entry_nline > 1) { 659 register_multiline_command(entry_text); 660 } else { 661 register_command(entry_text); 662 } 663 664 entry_nline = 0; 665 entry_text = ""; 666 } 667 668 function save_timestamp(line) { 669 if (is_resolve) { 670 # "history" format 671 if (line ~ /^ *[0-9]+\*? +__ble_time_[0-9]+__/) { 672 sub(/^ *[0-9]+\*? +__ble_time_/, "", line); 673 sub(/__.*$/, "", line); 674 entry_time = line; 675 } 676 } else { 677 # "history -w" format 678 if (line ~ /^#[0-9]/) { 679 sub(/^#/, "", line); 680 sub(/[^0-9].*$/, "", line); 681 entry_time = line; 682 } 683 } 684 } 685 686 { 687 if (is_resolve) { 688 save_timestamp($0); 689 if (sub(/^ *[0-9]+\*? +(__ble_time_[0-9]+__|\?\?|.+: invalid timestamp)/, "", $0)) 690 flush_entry(); 691 entry_text = ++entry_nline == 1 ? $0 : entry_text "\n" $0; 692 } else { 693 if ($0 ~ /^#[0-9]/) { 694 save_timestamp($0); 695 next; 696 } else { 697 flush_entry(); 698 entry_text = $0; 699 entry_nline = 1; 700 } 701 } 702 } 703 704 END { 705 flush_entry(); 706 write_flush(); 707 if (is_resolve) 708 print "builtin history -a /dev/null" > filename_source 709 print "multiline_count=" multiline_count; 710 print "modification_count=" modification_count; 711 } 712 ' 713 } 714 ## @fn ble/history:bash/resolve-multiline/.cleanup 715 ## @var[in] tmpfile_base 716 function ble/history:bash/resolve-multiline/.cleanup { 717 local file 718 for file in "$tmpfile_base".*; do : >| "$file"; done 719 } 720 function ble/history:bash/resolve-multiline/.worker { 721 local HISTTIMEFORMAT=__ble_time_%s__ 722 local -x tmpfile_base=$_ble_base_run/$$.history.mlfix 723 local multiline_count=0 modification_count=0 724 builtin eval -- "$(ble/builtin/history/.dump | ble/history:bash/resolve-multiline/.awk resolve 2>/dev/null)" 725 if ((modification_count)); then 726 ble/bin/mv -f "$tmpfile_base.part" "$tmpfile_base.sh" 727 else 728 ble/util/print : >| "$tmpfile_base.sh" 729 fi 730 } 731 function ble/history:bash/resolve-multiline/.load { 732 local tmpfile_base=$_ble_base_run/$$.history.mlfix 733 local HISTCONTROL= HISTSIZE= HISTIGNORE= 734 source "$tmpfile_base.sh" 735 ble/history:bash/resolve-multiline/.cleanup 736 } 737 738 ## @fn ble/history:bash/resolve-multiline opts 739 ## @param[in] opts 740 ## async 741 ## 非同期で読み取ります。 742 function ble/history:bash/resolve-multiline.impl { 743 local opts=$1 744 local opt_async=; [[ :$opts: == *:async:* ]] && opt_async=1 745 746 local history_tmpfile=$_ble_base_run/$$.history.mlfix.sh 747 [[ $opt_async || :$opts: == *:init:* ]] || _ble_history_mlfix_resume=0 748 749 [[ ! $opt_async ]] && ((_ble_history_mlfix_resume<=4)) && 750 blehook/invoke history_message "resolving multiline history ..." 751 while :; do 752 case $_ble_history_mlfix_resume in 753 754 (0) if [[ $opt_async ]] && ble/builtin/history/is-empty; then 755 # Note: bashrc の中では resolve-multiline はしない。 756 # 一旦 bash が履歴を読み込んだ後に再度試す。 757 ble/util/idle.wait-user-input 758 ((_ble_history_mlfix_resume++)) 759 return 147 760 fi 761 ((_ble_history_mlfix_resume++)) ;; 762 763 (1) # 履歴ファイル生成を Background で開始 764 if [[ $_ble_history_mlfix_bgpid ]]; then 765 builtin kill -9 "$_ble_history_mlfix_bgpid" &>/dev/null 766 _ble_history_mlfix_bgpid= 767 fi 768 769 : >| "$history_tmpfile" 770 if [[ $opt_async ]]; then 771 _ble_history_mlfix_bgpid=$(ble/util/nohup 'ble/history:bash/resolve-multiline/.worker' print-bgpid) 772 773 function ble/history:bash/resolve-multiline/.worker-completed { 774 local history_tmpfile=$_ble_base_run/$$.history.mlfix.sh 775 [[ -s $history_tmpfile ]] || ! builtin kill -0 "$_ble_history_mlfix_bgpid" 776 } &>/dev/null 777 778 ((_ble_history_mlfix_resume++)) 779 else 780 ble/history:bash/resolve-multiline/.worker 781 ((_ble_history_mlfix_resume+=3)) 782 fi ;; 783 784 (2) if [[ $opt_async ]] && ble/util/is-running-in-idle; then 785 ble/util/idle.wait-condition ble/history:bash/resolve-multiline/.worker-completed 786 ((_ble_history_mlfix_resume++)) 787 return 147 788 fi 789 ((_ble_history_mlfix_resume++)) ;; 790 791 # Note: async でバックグラウンドプロセスを起動した後に、直接 (sync で) 792 # 呼び出された時、未だ処理が完了していなくても次のステップに進んでしまうので、 793 # 此処で条件が満たされるのを待つ (#D0745) 794 (3) while ! ble/history:bash/resolve-multiline/.worker-completed; do 795 ble/util/msleep 50 796 [[ $opt_async ]] && ! ble/util/idle/IS_IDLE && return 148 797 done 798 ((_ble_history_mlfix_resume++)) ;; 799 800 # 80ms history 再構築 (47000項目) 801 (4) _ble_history_mlfix_bgpid= 802 ble/history:bash/resolve-multiline/.load 803 [[ $opt_async ]] || blehook/invoke history_message 804 ((_ble_history_mlfix_resume++)) 805 return 0 ;; 806 807 (*) return 1 ;; 808 esac 809 810 [[ $opt_async ]] && ! ble/util/idle/IS_IDLE && return 148 811 done 812 } 813 814 function ble/history:bash/resolve-multiline { 815 [[ $_ble_history_mlfix_done ]] && return 0 816 if [[ $1 == sync ]]; then 817 ((_ble_bash>=40000)) && [[ $BASHPID != $$ ]] && return 0 818 ble/builtin/history/is-empty && return 0 819 fi 820 821 ble/history:bash/resolve-multiline.impl "$@"; local ext=$? 822 ((ext)) && return "$ext" 823 _ble_history_mlfix_done=1 824 return 0 825 } 826 ((_ble_bash>=40000)) && 827 ble/util/idle.push 'ble/history:bash/resolve-multiline async' 828 829 blehook history_reset_background!=_ble_history_mlfix_resume=0 830 831 function ble/history:bash/resolve-multiline/readfile { 832 local filename=$1 833 local -x tmpfile_base=$_ble_base_run/$$.history.read 834 ble/history:bash/resolve-multiline/.awk read < "$filename" &>/dev/null 835 source "$tmpfile_base.part" 836 ble/history:bash/resolve-multiline/.cleanup 837 } 838 else 839 function ble/history:bash/resolve-multiline/readfile { builtin history -r "$filename"; } 840 function ble/history:bash/resolve-multiline { ((1)); } 841 fi 842 843 # Note: 複数行コマンドは eval -- $'' の形に変換して 844 # 書き込みたいので自前で処理する。 845 function ble/history:bash/unload.hook { 846 ble/util/is-running-in-subshell && return 0 847 if shopt -q histappend &>/dev/null; then 848 ble/builtin/history -a 849 else 850 ble/builtin/history -w 851 fi 852 } 853 blehook unload!=ble/history:bash/unload.hook 854 855 function ble/history:bash/reset { 856 if ((_ble_bash>=40000)); then 857 _ble_history_load_done= 858 ble/history:bash/clear-background-load 859 ble/util/idle.push 'ble/history:bash/initialize async' 860 elif ((_ble_bash>=30100)) && [[ $bleopt_history_lazyload ]]; then 861 _ble_history_load_done= 862 else 863 # * history-load は initialize ではなく attach で行う。 864 # detach してから attach する間に 865 # 追加されたエントリがあるかもしれないので。 866 # * bash-3.0 では history -s は最近の履歴項目を置換するだけなので、 867 # 履歴項目は全て自分で処理する必要がある。 868 # つまり、初めから load しておかなければならない。 869 ble/history:bash/initialize 870 fi 871 } 872 873 874 #------------------------------------------------------------------------------ 875 # ble/builtin/history @history.bash.builtin 876 877 function ble/builtin/history/.touch-histfile { 878 local touch=$_ble_base_run/$$.history.touch 879 : >| "$touch" 880 } 881 882 # in def.sh 883 # @hook history_change 884 885 # Note: #D1126 一度置き換えたら戻せない。二回は初期化しない。 886 if [[ ! ${_ble_builtin_history_initialized+set} ]]; then 887 _ble_builtin_history_initialized= 888 _ble_builtin_history_histnew_count=0 889 _ble_builtin_history_histapp_count=0 890 ## @var _ble_builtin_history_wskip 891 ## 履歴のどの行までがファイルに書き込み済みの行かを管理する変数です。 892 ## @var _ble_builtin_history_prevmax 893 ## 最後の ble/builtin/history における builtin history の項目番号 894 _ble_builtin_history_wskip=0 895 _ble_builtin_history_prevmax=0 896 897 ## 898 ## 以下の関数は各ファイルに関して何処まで読み取ったかを記録します。 899 ## 900 ## @fn ble/builtin/history/.get-rskip file 901 ## @param[in] file 902 ## @var[out] rskip 903 ## @fn ble/builtin/history/.set-rskip file value 904 ## @param[in] file 905 ## @fn ble/builtin/history/.add-rskip file delta 906 ## @param[in] file 907 ## 908 builtin eval -- "${_ble_util_gdict_declare//NAME/_ble_builtin_history_rskip_dict}" 909 function ble/builtin/history/.get-rskip { 910 local file=$1 ret 911 ble/gdict#get _ble_builtin_history_rskip_dict "$file" 912 rskip=$ret 913 } 914 function ble/builtin/history/.set-rskip { 915 local file=$1 916 ble/gdict#set _ble_builtin_history_rskip_dict "$file" "$2" 917 } 918 function ble/builtin/history/.add-rskip { 919 local file=$1 ret 920 # Note: 当初 ((dict[\$file]+=$2)) の形式を使っていたが、これは 921 # shopt -s assoc_expand_once の場合に動作しない事が判明したので、 922 # 一旦、別の変数で計算してから代入する事にする。 923 ble/gdict#get _ble_builtin_history_rskip_dict "$file" 924 ((ret+=$2)) 925 ble/gdict#set _ble_builtin_history_rskip_dict "$file" "$ret" 926 } 927 fi 928 929 ## @fn ble/builtin/history/.initialize opts 930 ## @param[in] opts 931 ## skip0 ... Bash 初期化処理 (bashrc) を抜け出ていると判定できない状態で、 932 ## 履歴が一件も読み込まれていない時はスキップします。 933 function ble/builtin/history/.initialize { 934 [[ $_ble_builtin_history_initialized ]] && return 0 935 local line; ble/util/assign line 'builtin history 1' 936 [[ ! $_ble_decode_hook_count && ! $line && :$1: == *:skip0:* ]] && return 1 937 _ble_builtin_history_initialized=1 938 939 local histnew=$_ble_base_run/$$.history.new 940 : >| "$histnew" 941 942 if [[ $line ]]; then 943 # Note: #D1126 ble.sh ロード前に追加された履歴項目があれば保存する。 944 local histini=$_ble_base_run/$$.history.ini 945 local histapp=$_ble_base_run/$$.history.app 946 HISTTIMEFORMAT=1 builtin history -a "$histini" 947 if [[ -s $histini ]]; then 948 ble/bin/sed '/^#\([0-9].*\)/{s// 0 __ble_time_\1__/;N;s/\n//;}' "$histini" >> "$histapp" 949 : >| "$histini" 950 fi 951 else 952 # 履歴が読み込まれていなければ強制的に読み込む 953 ble/builtin/history/option:r 954 fi 955 956 local histfile=${HISTFILE-} rskip=0 957 [[ -e $histfile ]] && rskip=$(ble/bin/wc -l "$histfile" 2>/dev/null) 958 ble/string#split-words rskip "$rskip" 959 local min; ble/builtin/history/.get-min 960 local max; ble/builtin/history/.get-max 961 ((max&&max-min+1<rskip&&(rskip=max-min+1))) 962 _ble_builtin_history_wskip=$max 963 _ble_builtin_history_prevmax=$max 964 ble/builtin/history/.set-rskip "$histfile" "$rskip" 965 return 0 966 } 967 ## @fn ble/builtin/history/.delete-range beg end 968 function ble/builtin/history/.delete-range { 969 local beg=$1 end=${2:-$1} 970 if ((_ble_bash>=50000&&beg<end)); then 971 builtin history -d "$beg-$end" 972 else 973 local i 974 for ((i=end;i>=beg;i--)); do 975 builtin history -d "$i" 976 done 977 fi 978 } 979 ## @fn ble/builtin/history/.check-uncontrolled-change [filename opts] 980 ## ble/builtin/history の管理外で履歴が読み込まれた時、 981 ## それを history -a の対象から除外する為に wskip を更新する。 982 function ble/builtin/history/.check-uncontrolled-change { 983 [[ $_ble_decode_bind_state == none ]] && return 0 984 local filename=${1-} opts=${2-} prevmax=$_ble_builtin_history_prevmax 985 local max; ble/builtin/history/.get-max 986 if ((max!=prevmax)); then 987 if [[ $filename && :$opts: == *:append:* ]] && ((_ble_builtin_history_wskip<prevmax&&prevmax<max)); then 988 # 最後に管理下で追加された事を確認した範囲 wskip..prevmax を書き込む。 989 ( 990 ble/builtin/history/.delete-range "$((prevmax+1))" "$max" 991 ble/builtin/history/.write "$filename" "$_ble_builtin_history_wskip" append:fetch 992 ) 993 fi 994 _ble_builtin_history_wskip=$max 995 _ble_builtin_history_prevmax=$max 996 fi 997 } 998 ## @fn ble/builtin/history/.load-recent-entries count 999 ## history の最新 count 件を配列 _ble_history に読み込みます。 1000 function ble/builtin/history/.load-recent-entries { 1001 [[ $_ble_decode_bind_state == none ]] && return 0 1002 1003 local delta=$1 1004 ((delta>0)) || return 0 1005 1006 if [[ ! $_ble_history_load_done ]]; then 1007 # history load が完了していなければ読み途中のデータを破棄して戻る 1008 ble/history:bash/clear-background-load 1009 _ble_history_count= 1010 return 0 1011 fi 1012 1013 # 追加項目が大量にある場合には background で完全再初期化する 1014 if ((_ble_bash>=40000&&delta>=10000)); then 1015 ble/history:bash/reset 1016 return 0 1017 fi 1018 1019 ble/history:bash/load append:count=$delta 1020 1021 local ocount=$_ble_history_count ncount=${#_ble_history[@]} 1022 ((_ble_history_index==_ble_history_count)) && _ble_history_index=$ncount 1023 _ble_history_count=$ncount 1024 ble/history/.update-position 1025 blehook/invoke history_change insert "$ocount" "$delta" 1026 } 1027 ## @fn ble/builtin/history/.read file [skip [fetch]] 1028 function ble/builtin/history/.read { 1029 local file=$1 skip=${2:-0} fetch=$3 1030 local -x histnew=$_ble_base_run/$$.history.new 1031 if [[ -s $file ]]; then 1032 local script=$(ble/bin/awk -v skip="$skip" ' 1033 BEGIN { histnew = ENVIRON["histnew"]; count = 0; } 1034 NR <= skip { next; } 1035 { print $0 >> histnew; count++; } 1036 END { 1037 print "ble/builtin/history/.set-rskip \"$file\" " NR; 1038 print "((_ble_builtin_history_histnew_count+=" count "))"; 1039 } 1040 ' "$file") 1041 builtin eval -- "$script" 1042 else 1043 ble/builtin/history/.set-rskip "$file" 0 1044 fi 1045 if [[ ! $fetch && -s $histnew ]]; then 1046 local nline=$_ble_builtin_history_histnew_count 1047 ble/history:bash/resolve-multiline/readfile "$histnew" 1048 : >| "$histnew" 1049 _ble_builtin_history_histnew_count=0 1050 ble/builtin/history/.load-recent-entries "$nline" 1051 local max; ble/builtin/history/.get-max 1052 _ble_builtin_history_wskip=$max 1053 _ble_builtin_history_prevmax=$max 1054 fi 1055 } 1056 ## @fn ble/builtin/history/.write file [skip [opts]] 1057 function ble/builtin/history/.write { 1058 local -x file=$1 skip=${2:-0} opts=$3 1059 local -x histapp=$_ble_base_run/$$.history.app 1060 declare -p HISTTIMEFORMAT &>/dev/null 1061 local -x flag_timestamp=$(($?==0)) 1062 1063 local min; ble/builtin/history/.get-min 1064 local max; ble/builtin/history/.get-max 1065 ((skip<min-1&&(skip=min-1))) 1066 local delta=$((max-skip)) 1067 if ((delta>0)); then 1068 local HISTTIMEFORMAT=__ble_time_%s__ 1069 if [[ :$opts: == *:append:* ]]; then 1070 ble/builtin/history/.dump "$delta" >> "$histapp" 1071 ((_ble_builtin_history_histapp_count+=delta)) 1072 else 1073 ble/builtin/history/.dump "$delta" >| "$histapp" 1074 _ble_builtin_history_histapp_count=$delta 1075 fi 1076 fi 1077 1078 if [[ ! -e $file ]]; then 1079 (umask 077; : >| "$file") 1080 elif [[ :$opts: != *:append:* ]]; then 1081 : >| "$file" 1082 fi 1083 1084 if [[ :$opts: != *:fetch:* && -s $histapp ]]; then 1085 local apos=\' 1086 < "$histapp" ble/bin/awk ' 1087 BEGIN { 1088 file = ENVIRON["file"]; 1089 flag_timestamp = ENVIRON["flag_timestamp"]; 1090 timestamp = ""; 1091 mode = 0; 1092 } 1093 function flush_line() { 1094 if (!mode) return; 1095 mode = 0; 1096 if (text ~ /\n/) { 1097 gsub(/['"$apos"'\\]/, "\\\\&", text); 1098 gsub(/\n/, "\\n", text); 1099 gsub(/\t/, "\\t", text); 1100 text = "eval -- $'"$apos"'" text "'"$apos"'" 1101 } 1102 1103 if (timestamp != "") 1104 print timestamp >> file; 1105 print text >> file; 1106 } 1107 1108 function extract_timestamp(line) { 1109 if (!sub(/^ *[0-9]+\*? +__ble_time_/, "", line)) return ""; 1110 if (!sub(/__.*$/, "", line)) return ""; 1111 if (!(line ~ /^[0-9]+$/)) return ""; 1112 return "#" line; 1113 } 1114 1115 /^ *[0-9]+\*? +(__ble_time_[0-9]+__|\?\?|.+: invalid timestamp)?/ { 1116 flush_line(); 1117 1118 mode = 1; 1119 text = ""; 1120 if (flag_timestamp) 1121 timestamp = extract_timestamp($0); 1122 1123 sub(/^ *[0-9]+\*? +(__ble_time_[0-9]+__|\?\?|.+: invalid timestamp)?/, "", $0); 1124 } 1125 { text = text != "" ? text "\n" $0 : $0; } 1126 END { flush_line(); } 1127 ' 1128 ble/builtin/history/.add-rskip "$file" "$_ble_builtin_history_histapp_count" 1129 : >| "$histapp" 1130 _ble_builtin_history_histapp_count=0 1131 fi 1132 _ble_builtin_history_wskip=$max 1133 _ble_builtin_history_prevmax=$max 1134 } 1135 1136 ## @fn ble/builtin/history/array#delete-hindex array_name index... 1137 ## @param[in] index 1138 ## 昇順に並んでいる事と重複がない事を仮定する。 1139 function ble/builtin/history/array#delete-hindex { 1140 local array_name=$1; shift 1141 local script=' 1142 local -a out=() 1143 local i shift=0 1144 for i in "${!ARR[@]}"; do 1145 local delete= 1146 while (($#)); do 1147 if [[ $1 == *-* ]]; then 1148 local b=${1%-*} e=${1#*-} 1149 ((i<b)) && break 1150 if ((i<e)); then 1151 delete=1 # delete 1152 break 1153 else 1154 ((shift+=e-b)) 1155 shift 1156 fi 1157 else 1158 ((i<$1)) && break 1159 ((i==$1)) && delete=1 1160 ((shift++)) 1161 shift 1162 fi 1163 done 1164 [[ ! $delete ]] && 1165 out[i-shift]=${ARR[i]} 1166 done 1167 ARR=() 1168 for i in "${!out[@]}"; do ARR[i]=${out[i]}; done' 1169 builtin eval -- "${script//ARR/$array_name}" 1170 } 1171 ## @fn ble/builtin/history/array#insert-range array_name beg len 1172 function ble/builtin/history/array#insert-range { 1173 local array_name=$1 beg=$2 len=$3 1174 local script=' 1175 local -a out=() 1176 local i 1177 for i in "${!ARR[@]}"; do 1178 out[i<beg?beg:i+len]=${ARR[i]} 1179 done 1180 ARR=() 1181 for i in "${!out[@]}"; do ARR[i]=${out[i]}; done' 1182 builtin eval -- "${script//ARR/$array_name}" 1183 } 1184 blehook history_change!=ble/builtin/history/change.hook 1185 function ble/builtin/history/change.hook { 1186 local kind=$1; shift 1187 case $kind in 1188 (delete) 1189 ble/builtin/history/array#delete-hindex _ble_history_dirt "$@" ;; 1190 (clear) 1191 _ble_history_dirt=() ;; 1192 (insert) 1193 # Note: _ble_history, _ble_history_edit は別に更新される 1194 ble/builtin/history/array#insert-range _ble_history_dirt "$@" ;; 1195 esac 1196 } 1197 ## @fn ble/builtin/history/option:c 1198 function ble/builtin/history/option:c { 1199 ble/builtin/history/.initialize skip0 || return "$?" 1200 builtin history -c 1201 _ble_builtin_history_wskip=0 1202 _ble_builtin_history_prevmax=0 1203 if [[ $_ble_decode_bind_state != none ]]; then 1204 if [[ $_ble_history_load_done ]]; then 1205 _ble_history=() 1206 _ble_history_edit=() 1207 _ble_history_count=0 1208 _ble_history_index=0 1209 else 1210 # history load が完了していなければ読み途中のデータを破棄して戻る 1211 ble/history:bash/clear-background-load 1212 _ble_history_count= 1213 fi 1214 ble/history/.update-position 1215 blehook/invoke history_change clear 1216 fi 1217 } 1218 ## @fn ble/builtin/history/option:d index 1219 function ble/builtin/history/option:d { 1220 ble/builtin/history/.initialize skip0 || return "$?" 1221 local rex='^(-?[0-9]+)-(-?[0-9]+)$' 1222 if [[ $1 =~ $rex ]]; then 1223 local beg=${BASH_REMATCH[1]} end=${BASH_REMATCH[2]} 1224 else 1225 local beg=$(($1)) 1226 local end=$beg 1227 fi 1228 local min; ble/builtin/history/.get-min 1229 local max; ble/builtin/history/.get-max 1230 ((beg<0)) && ((beg+=max+1)); ((beg<min?(beg=min):(beg>max&&(beg=max)))) 1231 ((end<0)) && ((end+=max+1)); ((end<min?(end=min):(end>max&&(end=max)))) 1232 ((beg<=end)) || return 0 1233 1234 ble/builtin/history/.delete-range "$beg" "$end" 1235 if ((_ble_builtin_history_wskip>=end)); then 1236 ((_ble_builtin_history_wskip-=end-beg+1)) 1237 elif ((_ble_builtin_history_wskip>beg-1)); then 1238 ((_ble_builtin_history_wskip=beg-1)) 1239 fi 1240 1241 if [[ $_ble_decode_bind_state != none ]]; then 1242 if [[ $_ble_history_load_done ]]; then 1243 local N=${#_ble_history[@]} 1244 local b=$((beg-1+N-max)) e=$((end+N-max)) 1245 blehook/invoke history_change delete "$b-$e" 1246 if ((_ble_history_index>=e)); then 1247 ((_ble_history_index-=e-b)) 1248 elif ((_ble_history_index>=b)); then 1249 _ble_history_index=$b 1250 fi 1251 _ble_history=("${_ble_history[@]::b}" "${_ble_history[@]:e}") 1252 _ble_history_edit=("${_ble_history_edit[@]::b}" "${_ble_history_edit[@]:e}") 1253 _ble_history_count=${#_ble_history[@]} 1254 else 1255 # history load が完了していなければ読み途中のデータを破棄して戻る 1256 ble/history:bash/clear-background-load 1257 _ble_history_count= 1258 fi 1259 ble/history/.update-position 1260 fi 1261 local max; ble/builtin/history/.get-max 1262 _ble_builtin_history_prevmax=$max 1263 } 1264 function ble/builtin/history/.get-histfile { 1265 histfile=${1:-${HISTFILE-}} 1266 if [[ ! $histfile ]]; then 1267 local opt=-a 1268 [[ ${FUNCNAME[1]} == *:[!:] ]] && opt=-${FUNCNAME[1]##*:} 1269 if [[ ${1+set} ]]; then 1270 ble/util/print "ble/builtin/history $opt: the history filename is empty." >&2 1271 else 1272 ble/util/print "ble/builtin/history $opt: the history file is not specified." >&2 1273 fi 1274 return 1 1275 fi 1276 } 1277 ## @fn ble/builtin/history/option:a [filename] 1278 function ble/builtin/history/option:a { 1279 ble/builtin/history/.initialize skip0 || return "$?" 1280 local histfile; ble/builtin/history/.get-histfile "$@" || return "$?" 1281 ble/builtin/history/.check-uncontrolled-change "$histfile" append 1282 local rskip; ble/builtin/history/.get-rskip "$histfile" 1283 ble/builtin/history/.write "$histfile" "$_ble_builtin_history_wskip" append:fetch 1284 [[ -r $histfile ]] && ble/builtin/history/.read "$histfile" "$rskip" fetch 1285 ble/builtin/history/.write "$histfile" "$_ble_builtin_history_wskip" append 1286 builtin history -a /dev/null # Bash 終了時に書き込まない 1287 } 1288 ## @fn ble/builtin/history/option:n [filename] 1289 function ble/builtin/history/option:n { 1290 # HISTFILE が更新されていなければスキップ 1291 local histfile; ble/builtin/history/.get-histfile "$@" || return "$?" 1292 if [[ $histfile == ${HISTFILE-} ]]; then 1293 local touch=$_ble_base_run/$$.history.touch 1294 [[ $touch -nt ${HISTFILE-} ]] && return 0 1295 : >| "$touch" 1296 fi 1297 1298 ble/builtin/history/.initialize 1299 local rskip; ble/builtin/history/.get-rskip "$histfile" 1300 ble/builtin/history/.read "$histfile" "$rskip" 1301 } 1302 ## @fn ble/builtin/history/option:w [filename] 1303 function ble/builtin/history/option:w { 1304 ble/builtin/history/.initialize skip0 || return "$?" 1305 local histfile; ble/builtin/history/.get-histfile "$@" || return "$?" 1306 local rskip; ble/builtin/history/.get-rskip "$histfile" 1307 [[ -r $histfile ]] && ble/builtin/history/.read "$histfile" "$rskip" fetch 1308 ble/builtin/history/.write "$histfile" 0 1309 builtin history -a /dev/null # Bash 終了時に書き込まない 1310 } 1311 ## @fn ble/builtin/history/option:r [histfile] 1312 function ble/builtin/history/option:r { 1313 local histfile; ble/builtin/history/.get-histfile "$@" || return "$?" 1314 ble/builtin/history/.initialize 1315 ble/builtin/history/.read "$histfile" 0 1316 } 1317 ## @fn ble/builtin/history/option:p 1318 ## Workaround for bash-3.0 -- 5.0 bug 1319 ## (See memo.txt #D0233, #D0801, #D1091) 1320 function ble/builtin/history/option:p { 1321 # Note: auto-complete .search-history-light や 1322 # magic-space 等経由で history -p が呼び出されて、 1323 # その時に resolve-multiline が sync されると引っ掛かる。 1324 # 従って history -p では sync しない事に決めた (#D1121) 1325 # Note: bash-3 では background load ができないので 1326 # 最初に history -p が呼び出されるタイミングで初期化する事にする (#D1122) 1327 ((_ble_bash>=40000)) || ble/builtin/history/is-empty || 1328 ble/history:bash/resolve-multiline sync 1329 1330 # Note: history -p '' によって 履歴項目が減少するかどうかをチェックし、 1331 # もし履歴項目が減る状態になっている場合は履歴項目を増やしてから history -p を実行する。 1332 # 嘗てはサブシェルで評価していたが、そうすると置換指示子が記録されず 1333 # :& が正しく実行されないことになるのでこちらの実装に切り替える。 1334 local line1= line2= 1335 ble/util/assign line1 'HISTTIMEFORMAT= builtin history 1' 1336 builtin history -p -- '' &>/dev/null 1337 ble/util/assign line2 'HISTTIMEFORMAT= builtin history 1' 1338 if [[ $line1 != "$line2" ]]; then 1339 local rex_head='^[[:space:]]*[0-9]+\*?[[:space:]]*' 1340 [[ $line1 =~ $rex_head ]] && 1341 line1=${line1:${#BASH_REMATCH}} 1342 1343 if ((_ble_bash<30100)); then 1344 # Note: history -r するとそれまでの履歴項目が終了時に 1345 # .bash_history に反映されなくなるが、 1346 # Bash 3.0 では明示的に書き込んでいるので問題ない。 1347 local tmp=$_ble_base_run/$$.history.tmp 1348 printf '%s\n' "$line1" "$line1" >| "$tmp" 1349 builtin history -r "$tmp" 1350 else 1351 builtin history -s -- "$line1" 1352 builtin history -s -- "$line1" 1353 fi 1354 fi 1355 1356 builtin history -p -- "$@" 1357 } 1358 1359 bleopt/declare -v history_erasedups_limit 0 1360 : "${_ble_history_erasedups_base=}" 1361 1362 function ble/builtin/history/erasedups/update-base { 1363 if [[ ! ${_ble_history_erasedups_base:-} ]]; then 1364 _ble_history_erasedups_base=${#_ble_history[@]} 1365 else 1366 local value=${#_ble_history[@]} 1367 ((value<_ble_history_erasedups_base&&(_ble_history_erasedups_base=value))) 1368 fi 1369 } 1370 ## @fn ble/builtin/history/erasedups/.impl-for cmd 1371 ## @fn ble/builtin/history/erasedups/.impl-awk cmd 1372 ## @fn ble/builtin/history/erasedups/.impl-ranged cmd beg 1373 ## @param[in] cmd 1374 ## @var[in] N 1375 ## @var[in] HISTINDEX_NEXT 1376 ## @var[in] _ble_builtin_history_wskip 1377 ## @arr[out] delete_indices 1378 ## @var[out] shift_histindex_next 1379 ## @var[out] shift_wskip 1380 function ble/builtin/history/erasedups/.impl-for { 1381 local cmd=$1 1382 delete_indices=() 1383 shift_histindex_next=0 1384 shift_wskip=0 1385 1386 local i 1387 for ((i=0;i<N-1;i++)); do 1388 if [[ ${_ble_history[i]} == "$cmd" ]]; then 1389 builtin unset -v '_ble_history[i]' 1390 builtin unset -v '_ble_history_edit[i]' 1391 ble/array#push delete_indices "$i" 1392 ((i<_ble_builtin_history_wskip&&shift_wskip++)) 1393 ((i<HISTINDEX_NEXT&&shift_histindex_next++)) 1394 fi 1395 done 1396 if ((${#delete_indices[@]})); then 1397 _ble_history=("${_ble_history[@]}") 1398 _ble_history_edit=("${_ble_history_edit[@]}") 1399 fi 1400 } 1401 function ble/builtin/history/erasedups/.impl-awk { 1402 local cmd=$1 1403 delete_indices=() 1404 shift_histindex_next=0 1405 shift_wskip=0 1406 ((N)) || return 0 1407 1408 # select the fastest awk implementation 1409 local -x erasedups_nlfix_read= 1410 local awk writearray_options 1411 if ble/bin/awk0.available; then 1412 erasedups_nlfix_read= 1413 writearray_options=(-d '') 1414 awk=ble/bin/awk0 1415 else 1416 erasedups_nlfix_read=1 1417 writearray_options=(--nlfix) 1418 if ble/is-function ble/bin/mawk; then 1419 awk=ble/bin/mawk 1420 elif ble/is-function ble/bin/gawk; then 1421 awk=ble/bin/gawk 1422 else 1423 ble/builtin/history/erasedups/.impl-for "$@" 1424 return "$?" 1425 fi 1426 fi 1427 1428 local _ble_local_tmpfile 1429 ble/util/assign/mktmp; local otmp1=$_ble_local_tmpfile 1430 ble/util/assign/mktmp; local otmp2=$_ble_local_tmpfile 1431 ble/util/assign/mktmp; local itmp1=$_ble_local_tmpfile 1432 ble/util/assign/mktmp; local itmp2=$_ble_local_tmpfile 1433 1434 # Note: ジョブを無効にする為 subshell で実行 1435 ( ble/util/writearray "${writearray_options[@]}" _ble_history >| "$itmp1" & local pid1=$! 1436 ble/util/writearray "${writearray_options[@]}" _ble_history_edit >| "$itmp2" 1437 wait "$pid1" ) 1438 1439 local -x erasedups_cmd=$cmd 1440 local -x erasedups_out1=$otmp1 1441 local -x erasedups_out2=$otmp2 1442 local -x erasedups_histindex_next=$HISTINDEX_NEXT 1443 local -x erasedups_wskip=$_ble_builtin_history_wskip 1444 local awk_script=' 1445 '"$_ble_bin_awk_libES"' 1446 '"$_ble_bin_awk_libNLFIX"' 1447 1448 BEGIN { 1449 NLFIX_READ = ENVIRON["erasedups_nlfix_read"] != ""; 1450 cmd = ENVIRON["erasedups_cmd"]; 1451 out1 = ENVIRON["erasedups_out1"]; 1452 out2 = ENVIRON["erasedups_out2"]; 1453 histindex_next = ENVIRON["erasedups_histindex_next"]; 1454 wskip = ENVIRON["erasedups_wskip"]; 1455 1456 if (NLFIX_READ) 1457 es_initialize(); 1458 else 1459 RS = "\0"; 1460 1461 NLFIX_WRITE = _ble_bash < 50200; 1462 if (NLFIX_WRITE) nlfix_begin(); 1463 1464 hist_index = 0; 1465 edit_index = 0; 1466 delete_count = 0; 1467 shift_histindex_next = 0; 1468 shift_wskip = 0; 1469 } 1470 1471 function process_hist(elem) { 1472 if (hist_index < N - 1 && elem == cmd) { 1473 delete_indices[delete_count++] = hist_index; 1474 delete_table[hist_index] = 1; 1475 if (hist_index < wskip ) shift_wskip++; 1476 if (hist_index < histindex_next) shift_histindex_next++; 1477 } else { 1478 if (NLFIX_WRITE) 1479 nlfix_push(elem, out1); 1480 else 1481 printf("%s%c", elem, 0) > out1; 1482 } 1483 hist_index++; 1484 } 1485 1486 function process_edit(elem) { 1487 if (delete_count == 0) exit; 1488 if (NLFIX_WRITE) { 1489 if (edit_index == 0) { 1490 nlfix_end(out1); 1491 nlfix_begin(); 1492 } 1493 if (!delete_table[edit_index++]) 1494 nlfix_push(elem, out2); 1495 } else { 1496 if (!delete_table[edit_index++]) 1497 printf("%s%c", elem, 0) > out2; 1498 } 1499 } 1500 1501 mode == "edit" { 1502 if (NLFIX_READ) { 1503 edit[edit_index++] = $0; 1504 } else { 1505 process_edit($0); 1506 } 1507 next; 1508 } 1509 { 1510 if (NLFIX_READ) 1511 hist[hist_index++] = $0; 1512 else 1513 process_hist($0); 1514 } 1515 1516 END { 1517 if (NLFIX_READ) { 1518 n = split(hist[hist_index - 1], indices) 1519 for (i = 1; i <= n; i++) { 1520 elem = hist[indices[i]]; 1521 if (elem ~ /^\$'\''.*'\''/) 1522 hist[indices[i]] = es_unescape(substr(elem, 3, length(elem) - 3)); 1523 } 1524 n = hist_index - 1; 1525 hist_index = 0; 1526 for (i = 0; i < n; i++) 1527 process_hist(hist[i]); 1528 1529 n = split(edit[edit_index - 1], indices) 1530 for (i = 1; i <= n; i++) { 1531 elem = edit[indices[i]]; 1532 if (elem ~ /^\$'\''.*'\''/) 1533 edit[indices[i]] = es_unescape(substr(elem, 3, length(elem) - 3)); 1534 } 1535 n = edit_index - 1; 1536 edit_index = 0; 1537 for (i = 0; i < n; i++) 1538 process_edit(edit[i]); 1539 } 1540 1541 if (NLFIX_WRITE) nlfix_end(out2); 1542 1543 line = "delete_indices=(" 1544 for (i = 0; i < delete_count; i++) { 1545 if (i != 0) line = line " "; 1546 line = line delete_indices[i]; 1547 } 1548 line = line ")"; 1549 print line; 1550 print "shift_wskip=" shift_wskip; 1551 print "shift_histindex_next=" shift_histindex_next; 1552 } 1553 ' 1554 local awk_result 1555 ble/util/assign awk_result '"$awk" -v _ble_bash="$_ble_bash" -v N="$N" "$awk_script" "$itmp1" mode=edit "$itmp2"' 1556 builtin eval -- "$awk_result" 1557 if ((${#delete_indices[@]})); then 1558 if ((_ble_bash<50200)); then 1559 ble/util/readarray --nlfix _ble_history < "$otmp1" 1560 ble/util/readarray --nlfix _ble_history_edit < "$otmp2" 1561 else 1562 mapfile -d '' -t _ble_history < "$otmp1" 1563 mapfile -d '' -t _ble_history_edit < "$otmp2" 1564 fi 1565 fi 1566 _ble_local_tmpfile=$itmp2 ble/util/assign/rmtmp 1567 _ble_local_tmpfile=$itmp1 ble/util/assign/rmtmp 1568 _ble_local_tmpfile=$otmp2 ble/util/assign/rmtmp 1569 _ble_local_tmpfile=$otmp1 ble/util/assign/rmtmp 1570 } 1571 function ble/builtin/history/erasedups/.impl-ranged { 1572 local cmd=$1 beg=$2 1573 delete_indices=() 1574 shift_histindex_next=0 1575 shift_wskip=0 1576 1577 # Note: 自前で history -d を行って重複を削除するので erasedups は除去しておく。 1578 # 但し、一番最後の一致する要素だけは自分では削除しないので、後のhistory -s で 1579 # 余分な履歴項目が追加されない様に ignoredups を付加する。 1580 ble/path#remove HISTCONTROL erasedups 1581 HISTCONTROL=$HISTCONTROL:ignoredups 1582 1583 local i j=$beg 1584 for ((i=beg;i<N;i++)); do 1585 if ((i<N-1)) && [[ ${_ble_history[i]} == "$cmd" ]]; then 1586 ble/array#push delete_indices "$i" 1587 ((i<_ble_builtin_history_wskip&&shift_wskip++)) 1588 ((i<HISTINDEX_NEXT&&shift_histindex_next++)) 1589 else 1590 if ((i!=j)); then 1591 _ble_history[j]=${_ble_history[i]} 1592 _ble_history_edit[j]=${_ble_history_edit[i]} 1593 fi 1594 ((j++)) 1595 fi 1596 done 1597 for ((;j<N;j++)); do 1598 builtin unset -v '_ble_history[j]' 1599 builtin unset -v '_ble_history_edit[j]' 1600 done 1601 1602 if ((${#delete_indices[@]})); then 1603 local max; ble/builtin/history/.get-max 1604 local max_index=$((N-1)) 1605 for ((i=${#delete_indices[@]}-1;i>=0;i--)); do 1606 builtin history -d "$((delete_indices[i]-max_index+max))" 1607 done 1608 fi 1609 } 1610 ## @fn ble/builtin/history/erasedups cmd 1611 ## 指定したコマンドに一致する履歴項目を削除します。この呼出の後に history -s 1612 ## を呼び出す事を想定しています。但し、一番最後の一致する要素は削除しません。 1613 ## 1614 ## @var[in,out] HISTCONTROL 1615 ## @exit 9 1616 ## 重複する要素が一番最後の要素のみの時に終了ステータス 9 を返します。この 1617 ## 時、履歴追加を行っても履歴に変化は発生しないので、後続の history -s の呼 1618 ## び出しを省略してそのまま処理を終えても問題ありません。 1619 function ble/builtin/history/erasedups { 1620 local cmd=$1 1621 1622 local beg=0 N=${#_ble_history[@]} 1623 if [[ $bleopt_history_erasedups_limit ]]; then 1624 local limit=$((bleopt_history_erasedups_limit)) 1625 if ((limit<=0)); then 1626 ((beg=_ble_history_erasedups_base+limit)) 1627 else 1628 ((beg=N-1-limit)) 1629 fi 1630 ((beg<0)) && beg=0 1631 fi 1632 1633 local delete_indices shift_histindex_next shift_wskip 1634 if ((beg>=N)); then 1635 ble/path#remove HISTCONTROL erasedups 1636 return 0 1637 elif ((beg>0)); then 1638 ble/builtin/history/erasedups/.impl-ranged "$cmd" "$beg" 1639 else 1640 if ((_ble_bash>=40000&&N>=20000)); then 1641 ble/builtin/history/erasedups/.impl-awk "$cmd" 1642 else 1643 ble/builtin/history/erasedups/.impl-for "$cmd" 1644 fi 1645 fi 1646 1647 if ((${#delete_indices[@]})); then 1648 blehook/invoke history_change delete "${delete_indices[@]}" 1649 ((_ble_builtin_history_wskip-=shift_wskip)) 1650 [[ ${HISTINDEX_NEXT+set} ]] && ((HISTINDEX_NEXT-=shift_histindex_next)) 1651 else 1652 # 単に今回の history/option:s を無視すれば良いだけの時 1653 ((N)) && [[ ${_ble_history[N-1]} == "$cmd" ]] && return 9 1654 fi 1655 } 1656 1657 ## @fn ble/builtin/history/option:s 1658 function ble/builtin/history/option:s { 1659 ble/builtin/history/.initialize 1660 if [[ $_ble_decode_bind_state == none ]]; then 1661 builtin history -s -- "$@" 1662 return 0 1663 fi 1664 1665 local cmd=$1 1666 if [[ $HISTIGNORE ]]; then 1667 local pats pat 1668 ble/string#split pats : "$HISTIGNORE" 1669 for pat in "${pats[@]}"; do 1670 [[ $cmd == $pat ]] && return 0 1671 done 1672 # Note: 以降の処理では HISTIGNORE は無視する。trim した後のコマンドに対して 1673 # 改めて作用するのを防ぐ為。 1674 local HISTIGNORE= 1675 fi 1676 1677 # Note: ble/builtin/history/erasedups によって後の builtin history -s の為に 1678 # 時的に erasedups を除去する場合がある為ローカル変数に変えておく。また、 1679 # ignoreboth の処理の便宜の為にも内部的に書き換える。 1680 local HISTCONTROL=$HISTCONTROL 1681 1682 # Note: HISTIGNORE 及び ignorespace は trim 前に処理する。何故なら行頭の空白 1683 # などに意味を持たせたいから。ignoredups 及び erasedups は trim 後に作用させ 1684 # る。何故なら実際に履歴に登録されたコマンドと比較したいから。 1685 if [[ $HISTCONTROL ]]; then 1686 [[ :$HISTCONTROL: == *:ignoreboth:* ]] && 1687 HISTCONTROL=$HISTCONTROL:ignorespace:ignoredups 1688 if [[ :$HISTCONTROL: == *:ignorespace:* ]]; then 1689 [[ $cmd == [' ']* ]] && return 0 1690 fi 1691 1692 if [[ :$HISTCONTROL: == *:strip:* ]]; then 1693 local ret 1694 ble/string#rtrim "$cmd" 1695 ble/string#match "$ret" $'^[ \t]*(\n([ \t]*\n)*)?' 1696 cmd=${ret:${#BASH_REMATCH}} 1697 [[ $BASH_REMATCH == *$'\n'* && $cmd == *$'\n'* ]] && cmd=$'\n'$cmd 1698 fi 1699 fi 1700 1701 local use_bash300wa= 1702 if [[ $_ble_history_load_done ]]; then 1703 if [[ $HISTCONTROL ]]; then 1704 if [[ :$HISTCONTROL: == *:ignoredups:* ]]; then 1705 # Note: plain Bash では ignoredups を検出した時には erasedups は発生し 1706 # ない様なのでそれに倣う。 1707 local lastIndex=$((${#_ble_history[@]}-1)) 1708 ((lastIndex>=0)) && [[ $cmd == "${_ble_history[lastIndex]}" ]] && return 0 1709 fi 1710 if [[ :$HISTCONTROL: == *:erasedups:* ]]; then 1711 ble/builtin/history/erasedups "$cmd" 1712 (($?==9)) && return 0 1713 fi 1714 fi 1715 local topIndex=${#_ble_history[@]} 1716 _ble_history[topIndex]=$cmd 1717 _ble_history_edit[topIndex]=$cmd 1718 _ble_history_count=$((topIndex+1)) 1719 _ble_history_index=$_ble_history_count 1720 1721 # _ble_bash<30100 の時は必ずここを通る。 1722 # 初期化時に _ble_history_load_done=1 になるので。 1723 ((_ble_bash<30100)) && use_bash300wa=1 1724 else 1725 if [[ $HISTCONTROL ]]; then 1726 # 未だ履歴が初期化されていない場合は取り敢えず history -s に渡す。 1727 # history -s でも HISTCONTROL に対するフィルタはされる。 1728 # history -s で項目が追加されたかどうかはスクリプトからは分からないので 1729 # _ble_history_count をクリアして再計算する 1730 _ble_history_count= 1731 else 1732 # HISTCONTROL がなければ多分 history -s で必ず追加される。 1733 # _ble_history_count 取得済ならば更新。 1734 [[ $_ble_history_count ]] && 1735 ((_ble_history_count++)) 1736 fi 1737 fi 1738 ble/history/.update-position 1739 1740 if [[ $use_bash300wa ]]; then 1741 # bash < 3.1 workaround 1742 if [[ $cmd == *$'\n'* ]]; then 1743 # Note: 改行を含む場合は %q は常に $'' の形式になる。 1744 ble/util/sprintf cmd 'eval -- %q' "$cmd" 1745 fi 1746 local tmp=$_ble_base_run/$$.history.tmp 1747 [[ ${HISTFILE-} && ! $bleopt_history_share ]] && 1748 ble/util/print "$cmd" >> "${HISTFILE-}" 1749 ble/util/print "$cmd" >| "$tmp" 1750 builtin history -r "$tmp" 1751 else 1752 ble/history:bash/clear-background-load 1753 builtin history -s -- "$cmd" 1754 fi 1755 local max; ble/builtin/history/.get-max 1756 _ble_builtin_history_prevmax=$max 1757 } 1758 function ble/builtin/history { 1759 local set shopt; ble/base/.adjust-bash-options set shopt 1760 local opt_d= flag_error= 1761 local opt_c= opt_p= opt_s= 1762 local opt_a= flags= 1763 while [[ $1 == -* ]]; do 1764 local arg=$1; shift 1765 [[ $arg == -- ]] && break 1766 1767 if [[ $arg == --help ]]; then 1768 flags=h$flags 1769 continue 1770 fi 1771 1772 local i n=${#arg} 1773 for ((i=1;i<n;i++)); do 1774 local c=${arg:i:1} 1775 case $c in 1776 (c) opt_c=1 ;; 1777 (s) opt_s=1 ;; 1778 (p) opt_p=1 ;; 1779 (d) 1780 if ((!$#)); then 1781 ble/util/print 'ble/builtin/history: missing option argument for "-d".' >&2 1782 flag_error=1 1783 elif ((i+1<n)); then 1784 opt_d=${arg:i+1}; i=$n 1785 else 1786 opt_d=$1; shift 1787 fi ;; 1788 ([anwr]) 1789 if [[ $opt_a && $c != $opt_a ]]; then 1790 ble/util/print 'ble/builtin/history: cannot use more than one of "-anrw".' >&2 1791 flag_error=1 1792 elif ((i+1<n)); then 1793 opt_a=$c 1794 set -- "${arg:i+1}" "$@" 1795 else 1796 opt_a=$c 1797 fi ;; 1798 (*) 1799 ble/util/print "ble/builtin/history: unknown option \"-$c\"." >&2 1800 flag_error=1 ;; 1801 esac 1802 done 1803 done 1804 if [[ $flag_error ]]; then 1805 builtin history --usage 2>&1 1>/dev/null | ble/bin/grep ^history >&2 1806 ble/base/.restore-bash-options set shopt 1807 return 2 1808 fi 1809 1810 if [[ $flags == *h* ]]; then 1811 builtin history --help 1812 local ext=$? 1813 ble/base/.restore-bash-options set shopt 1814 return "$ext" 1815 fi 1816 1817 [[ ! $_ble_attached || $_ble_edit_exec_inside_userspace ]] && 1818 ble/base/adjust-BASH_REMATCH 1819 1820 # -cdanwr 1821 local flag_processed= 1822 if [[ $opt_c ]]; then 1823 ble/builtin/history/option:c 1824 flag_processed=1 1825 fi 1826 if [[ $opt_s ]]; then 1827 local IFS=$_ble_term_IFS 1828 ble/builtin/history/option:s "$*" 1829 flag_processed=1 1830 elif [[ $opt_d ]]; then 1831 ble/builtin/history/option:d "$opt_d" 1832 flag_processed=1 1833 elif [[ $opt_a ]]; then 1834 ble/builtin/history/option:"$opt_a" "$@" 1835 flag_processed=1 1836 fi 1837 if [[ $flag_processed ]]; then 1838 ble/base/.restore-bash-options set shopt 1839 return 0 1840 fi 1841 1842 # -p 1843 if [[ $opt_p ]]; then 1844 ble/builtin/history/option:p "$@" 1845 else 1846 builtin history "$@" 1847 fi; local ext=$? 1848 1849 [[ ! $_ble_attached || $_ble_edit_exec_inside_userspace ]] && 1850 ble/base/restore-BASH_REMATCH 1851 ble/base/.restore-bash-options set shopt 1852 return "$ext" 1853 } 1854 function history { ble/builtin/history "$@"; } 1855 1856 #============================================================================== 1857 # ble/history @history 1858 1859 ## @var _ble_history_prefix 1860 ## 1861 ## 現在どの履歴を対象としているかを保持する。 1862 ## 空文字列の時、コマンド履歴を対象とする。以下の変数を用いる。 1863 ## 1864 ## _ble_history 1865 ## _ble_history_index 1866 ## _ble_history_edit 1867 ## _ble_history_dirt 1868 ## 1869 ## 空でない文字列 prefix のとき、以下の変数を操作対象とする。 1870 ## 1871 ## ${prefix}_history 1872 ## ${prefix}_history_index 1873 ## ${prefix}_history_edit 1874 ## ${prefix}_history_dirt 1875 ## 1876 ## 何れの関数も _ble_history_prefix を適切に処理する必要がある。 1877 ## 1878 ## 実装のために配列 _ble_history_edit などを 1879 ## ローカルに定義して処理するときは、以下の注意点を守る必要がある。 1880 ## 1881 ## - その関数自身またはそこから呼び出される関数が、 1882 ## 履歴項目に対して副作用を持ってはならない。 1883 ## 1884 ## この要請の下で、各関数は呼び出し元のすり替えを意識せずに動作できる。 1885 ## 1886 _ble_history_prefix= 1887 1888 function ble/history/set-prefix { 1889 _ble_history_prefix=$1 1890 ble/history/.update-position 1891 } 1892 1893 _ble_history_COUNT= 1894 _ble_history_INDEX= 1895 function ble/history/.update-position { 1896 if [[ $_ble_history_prefix ]]; then 1897 builtin eval -- "_ble_history_COUNT=\${#${_ble_history_prefix}_history[@]}" 1898 ((_ble_history_INDEX=${_ble_history_prefix}_history_index)) 1899 else 1900 # 履歴読込完了前の時 1901 if [[ ! $_ble_history_load_done ]]; then 1902 if [[ ! $_ble_history_count ]]; then 1903 local min max 1904 ble/builtin/history/.get-min 1905 ble/builtin/history/.get-max 1906 ((_ble_history_count=max-min+1)) 1907 fi 1908 _ble_history_index=$_ble_history_count 1909 fi 1910 1911 _ble_history_COUNT=$_ble_history_count 1912 _ble_history_INDEX=$_ble_history_index 1913 fi 1914 } 1915 function ble/history/update-position { 1916 [[ $_ble_history_prefix$_ble_history_load_done ]] || 1917 ble/history/.update-position 1918 } 1919 1920 ## @hook history_leave (defined in def.sh) 1921 1922 function ble/history/onleave.fire { 1923 blehook/invoke history_leave "$@" 1924 } 1925 1926 ## called by ble-edit/initialize in Bash 3 1927 function ble/history/initialize { 1928 [[ ! $_ble_history_prefix ]] && 1929 ble/history:bash/initialize 1930 } 1931 function ble/history/get-count { 1932 local _ble_local_var=count 1933 [[ $1 == -v ]] && { _ble_local_var=$2; shift 2; } 1934 ble/history/.update-position 1935 (($_ble_local_var=_ble_history_COUNT)) 1936 } 1937 function ble/history/get-index { 1938 local _ble_local_var=index 1939 [[ $1 == -v ]] && { _ble_local_var=$2; shift 2; } 1940 ble/history/.update-position 1941 (($_ble_local_var=_ble_history_INDEX)) 1942 } 1943 function ble/history/set-index { 1944 _ble_history_INDEX=$1 1945 ((${_ble_history_prefix:-_ble}_history_index=_ble_history_INDEX)) 1946 } 1947 function ble/history/get-entry { 1948 local _ble_local_var=entry 1949 [[ $1 == -v ]] && { _ble_local_var=$2; shift 2; } 1950 if [[ $_ble_history_prefix$_ble_history_load_done ]]; then 1951 builtin eval -- "$_ble_local_var=\${${_ble_history_prefix:-_ble}_history[\$1]}" 1952 else 1953 builtin eval -- "$_ble_local_var=" 1954 fi 1955 } 1956 function ble/history/get-edited-entry { 1957 local _ble_local_var=entry 1958 [[ $1 == -v ]] && { _ble_local_var=$2; shift 2; } 1959 if [[ $_ble_history_prefix$_ble_history_load_done ]]; then 1960 builtin eval -- "$_ble_local_var=\${${_ble_history_prefix:-_ble}_history_edit[\$1]}" 1961 else 1962 builtin eval -- "$_ble_local_var=\$_ble_edit_str" 1963 fi 1964 } 1965 ## @fn ble/history/set-edited-entry index str 1966 function ble/history/set-edited-entry { 1967 ble/history/initialize 1968 local index=$1 str=$2 1969 local code=' 1970 # store 1971 if [[ ! ${PREFIX_history_edit[index]+set} || ${PREFIX_history_edit[index]} != "$str" ]]; then 1972 PREFIX_history_edit[index]=$str 1973 PREFIX_history_dirt[index]=1 1974 fi' 1975 builtin eval -- "${code//PREFIX/${_ble_history_prefix:-_ble}}" 1976 } 1977 1978 ## @fn ble/history/.add-command-history command 1979 ## @var[in,out] HISTINDEX_NEXT 1980 ## used by ble/widget/accept-and-next to get modified next-entry positions 1981 function ble/history/.add-command-history { 1982 # 注意: bash-3.2 未満では何故か bind -x の中では常に history off になっている。 1983 [[ -o history ]] || ((_ble_bash<30200)) || return 1 1984 1985 # Note: mc (midnight commander) が初期化スクリプトを送ってくる #D1392 1986 [[ $MC_SID == $$ && $_ble_edit_LINENO -le 2 && ( $1 == *PROMPT_COMMAND=* || $1 == *PS1=* ) ]] && return 1 1987 1988 if [[ $_ble_history_load_done ]]; then 1989 # 登録・不登録に拘わらず取り敢えず初期化 1990 _ble_history_index=${#_ble_history[@]} 1991 ble/history/.update-position 1992 1993 # _ble_history_edit を未編集状態に戻す 1994 local index 1995 for index in "${!_ble_history_dirt[@]}"; do 1996 _ble_history_edit[index]=${_ble_history[index]} 1997 done 1998 _ble_history_dirt=() 1999 2000 # 同時に _ble_edit_undo も初期化する。 2001 ble-edit/undo/clear-all 2002 fi 2003 2004 if [[ $bleopt_history_share ]]; then 2005 ble/builtin/history/option:n 2006 ble/builtin/history/option:s "$1" 2007 ble/builtin/history/option:a 2008 ble/builtin/history/.touch-histfile 2009 else 2010 ble/builtin/history/option:s "$1" 2011 fi 2012 } 2013 2014 function ble/history/add { 2015 local command=$1 2016 ((bleopt_history_limit_length>0&&${#command}>bleopt_history_limit_length)) && return 1 2017 2018 if [[ $_ble_history_prefix ]]; then 2019 local code=' 2020 # PREFIX_history_edit を未編集状態に戻す 2021 local index 2022 for index in "${!PREFIX_history_dirt[@]}"; do 2023 PREFIX_history_edit[index]=${PREFIX_history[index]} 2024 done 2025 PREFIX_history_dirt=() 2026 2027 local topIndex=${#PREFIX_history[@]} 2028 PREFIX_history[topIndex]=$command 2029 PREFIX_history_edit[topIndex]=$command 2030 PREFIX_history_index=$((++topIndex)) 2031 _ble_history_COUNT=$topIndex 2032 _ble_history_INDEX=$topIndex' 2033 builtin eval -- "${code//PREFIX/$_ble_history_prefix}" 2034 else 2035 blehook/invoke ADDHISTORY "$command" && 2036 ble/history/.add-command-history "$command" 2037 fi 2038 } 2039 2040 #------------------------------------------------------------------------------ 2041 # ble/history/search @history.search 2042 2043 ## @fn ble/history/isearch-forward opts 2044 ## @fn ble/history/isearch-backward opts 2045 ## @fn ble/history/isearch-backward-blockwise opts 2046 ## 2047 ## backward-search-history-blockwise does blockwise search 2048 ## as a workaround for bash slow array access 2049 ## 2050 ## @param[in] opts 2051 ## コロン区切りのオプションです。 2052 ## 2053 ## regex 正規表現による検索を行います。 2054 ## glob グロブパターンによる一致を試みます。 2055 ## head 固定文字列に依る先頭一致を試みます。 2056 ## tail 固定文字列に依る終端一致を試みます。 2057 ## condition 述語コマンドを評価 (eval) して一致を試みます。 2058 ## predicate 述語関数を呼び出して一致を試みます。 2059 ## これらの内の何れか一つを指定します。 2060 ## 何も指定しない場合は固定文字列の部分一致を試みます。 2061 ## 2062 ## stop_check 2063 ## ユーザの入力があった時に終了ステータス 148 で中断します。 2064 ## 2065 ## progress 2066 ## 検索の途中経過を表示します。 2067 ## 後述の isearch_progress_callback 変数に指定された関数を呼び出します。 2068 ## 2069 ## backward 2070 ## 内部使用のオプションです。 2071 ## forward-search-history に対して指定して、後方検索を行う事を指定します。 2072 ## 2073 ## cyclic 2074 ## 履歴の端まで達した時、履歴の反対側の端から検索を続行します。 2075 ## 一致が見つからずに start の直前の要素まで達した時に失敗します。 2076 ## 2077 ## @var[in] _ble_history_edit 2078 ## 検索対象の配列と全体の検索開始位置を指定します。 2079 ## @var[in] start 2080 ## 全体の検索開始位置を指定します。 2081 ## 2082 ## @var[in] needle 2083 ## 検索文字列を指定します。 2084 ## 2085 ## opts に regex または glob を指定した場合は、 2086 ## それぞれ正規表現またはグロブパターンを指定します。 2087 ## 2088 ## opts に condition を指定した場合は needle を述語コマンドと解釈します。 2089 ## 変数 LINE 及び INDEX にそれぞれ行の内容と履歴番号を設定して eval されます。 2090 ## 2091 ## opts に predicate を指定した場合は needle を述語関数の関数名と解釈します。 2092 ## 指定する述語関数は検索が一致した時に成功し、それ以外の時に失敗する関数です。 2093 ## 第1引数と第2引数に行の内容と履歴番号を指定して関数が呼び出されます。 2094 ## 2095 ## @var[in,out] index 2096 ## 今回の呼び出しの検索開始位置を指定します。 2097 ## 一致が成功したとき見つかった位置を返します。 2098 ## 一致が中断されたとき次の位置 (再開時に最初に検査する位置) を返します。 2099 ## 2100 ## @var[in,out] isearch_time 2101 ## 2102 ## @var[in] isearch_progress_callback 2103 ## progress の表示時に呼び出す関数名を指定します。 2104 ## 第一引数には現在の検索位置 (history index) を指定します。 2105 ## 2106 ## @exit 2107 ## 見つかったときに 0 を返します。 2108 ## 見つからなかったときに 1 を返します。 2109 ## 中断された時に 148 を返します。 2110 ## 2111 function ble/history/.read-isearch-options { 2112 local opts=$1 2113 2114 search_type=fixed 2115 case :$opts: in 2116 (*:regex:*) search_type=regex ;; 2117 (*:glob:*) search_type=glob ;; 2118 (*:head:*) search_type=head ;; 2119 (*:tail:*) search_type=tail ;; 2120 (*:condition:*) search_type=condition ;; 2121 (*:predicate:*) search_type=predicate ;; 2122 esac 2123 2124 [[ :$opts: != *:stop_check:* ]]; has_stop_check=$? 2125 [[ :$opts: != *:progress:* ]]; has_progress=$? 2126 [[ :$opts: != *:backward:* ]]; has_backward=$? 2127 } 2128 function ble/history/isearch-backward-blockwise { 2129 local opts=$1 2130 local search_type has_stop_check has_progress has_backward 2131 ble/history/.read-isearch-options "$opts" 2132 2133 ble/history/initialize 2134 if [[ $_ble_history_prefix ]]; then 2135 local -a _ble_history_edit 2136 builtin eval "_ble_history_edit=(\"\${${_ble_history_prefix}_history_edit[@]}\")" 2137 fi 2138 2139 local isearch_block=1000 # 十分高速なのでこれぐらい大きくてOK 2140 local isearch_quantum=$((isearch_block*2)) # 倍数である必要有り 2141 local irest block j i=$index 2142 index= 2143 2144 local flag_icase=; [[ :$opts: == *:ignore-case:* ]] && flag_icase=1 2145 2146 local flag_cycled= range_min range_max 2147 while :; do 2148 if ((i<=start)); then 2149 range_min=0 range_max=$start 2150 else 2151 flag_cycled=1 2152 range_min=$((start+1)) range_max=$i 2153 fi 2154 2155 while ((i>=range_min)); do 2156 ((block=range_max-i, 2157 block<5&&(block=5), 2158 block>i+1-range_min&&(block=i+1-range_min), 2159 irest=isearch_block-isearch_time%isearch_block, 2160 block>irest&&(block=irest))) 2161 2162 [[ $flag_icase ]] && shopt -s nocasematch 2163 case $search_type in 2164 (regex) for ((j=i-block;++j<=i;)); do 2165 [[ ${_ble_history_edit[j]} =~ $needle ]] && index=$j 2166 done ;; 2167 (glob) for ((j=i-block;++j<=i;)); do 2168 [[ ${_ble_history_edit[j]} == $needle ]] && index=$j 2169 done ;; 2170 (head) for ((j=i-block;++j<=i;)); do 2171 [[ ${_ble_history_edit[j]} == "$needle"* ]] && index=$j 2172 done ;; 2173 (tail) for ((j=i-block;++j<=i;)); do 2174 [[ ${_ble_history_edit[j]} == *"$needle" ]] && index=$j 2175 done ;; 2176 (condition) builtin eval "function ble-edit/isearch/.search-block.proc { 2177 local LINE INDEX 2178 for ((j=i-block;++j<=i;)); do 2179 LINE=\${_ble_history_edit[j]} INDEX=\$j 2180 { $needle; } && index=\$j 2181 done 2182 }" 2183 ble-edit/isearch/.search-block.proc ;; 2184 (predicate) for ((j=i-block;++j<=i;)); do 2185 "$needle" "${_ble_history_edit[j]}" "$j" && index=$j 2186 done ;; 2187 (*) for ((j=i-block;++j<=i;)); do 2188 [[ ${_ble_history_edit[j]} == *"$needle"* ]] && index=$j 2189 done ;; 2190 esac 2191 [[ $flag_icase ]] && shopt -u nocasematch 2192 2193 ((isearch_time+=block)) 2194 [[ $index ]] && return 0 2195 2196 ((i-=block)) 2197 if ((has_stop_check&&isearch_time%isearch_block==0)) && ble/decode/has-input; then 2198 index=$i 2199 return 148 2200 elif ((has_progress&&isearch_time%isearch_quantum==0)); then 2201 "$isearch_progress_callback" "$i" 2202 fi 2203 done 2204 2205 if [[ ! $flag_cycled && :$opts: == *:cyclic:* ]]; then 2206 ((i=${#_ble_history_edit[@]}-1)) 2207 ((start<i)) || return 1 2208 else 2209 return 1 2210 fi 2211 done 2212 } 2213 function ble/history/isearch-forward.impl { 2214 local opts=$1 2215 local search_type has_stop_check has_progress has_backward 2216 ble/history/.read-isearch-options "$opts" 2217 2218 ble/history/initialize 2219 if [[ $_ble_history_prefix ]]; then 2220 local -a _ble_history_edit 2221 builtin eval "_ble_history_edit=(\"\${${_ble_history_prefix}_history_edit[@]}\")" 2222 fi 2223 2224 local flag_icase=; [[ :$opts: == *:ignore-case:* ]] && flag_icase=1 2225 2226 while :; do 2227 local flag_cycled= expr_cond expr_incr 2228 if ((has_backward)); then 2229 if ((index<=start)); then 2230 expr_cond='index>=0' expr_incr='index--' 2231 else 2232 expr_cond='index>start' expr_incr='index--' flag_cycled=1 2233 fi 2234 else 2235 if ((index>=start)); then 2236 expr_cond="index<${#_ble_history_edit[@]}" expr_incr='index++' 2237 else 2238 expr_cond="index<start" expr_incr='index++' flag_cycled=1 2239 fi 2240 fi 2241 2242 [[ $flag_icase ]] && shopt -s nocasematch 2243 case $search_type in 2244 (regex) 2245 #%define search_loop 2246 for ((;expr_cond;expr_incr)); do 2247 ((isearch_time++,has_stop_check&&isearch_time%100==0)) && 2248 ble/decode/has-input && return 148 2249 @ && return 0 2250 ((has_progress&&isearch_time%1000==0)) && 2251 "$isearch_progress_callback" "$index" 2252 done ;; 2253 #%end 2254 #%expand search_loop.r/@/[[ ${_ble_history_edit[index]} =~ $needle ]]/ 2255 (glob) 2256 #%expand search_loop.r/@/[[ ${_ble_history_edit[index]} == $needle ]]/ 2257 (head) 2258 #%expand search_loop.r/@/[[ ${_ble_history_edit[index]} == "$needle"* ]]/ 2259 (tail) 2260 #%expand search_loop.r/@/[[ ${_ble_history_edit[index]} == *"$needle" ]]/ 2261 (condition) 2262 #%expand search_loop.r/@/LINE=${_ble_history_edit[index]} INDEX=$index builtin eval -- "$needle"/ 2263 (predicate) 2264 #%expand search_loop.r/@/"$needle" "${_ble_history_edit[index]}" "$index"/ 2265 (*) 2266 #%expand search_loop.r/@/[[ ${_ble_history_edit[index]} == *"$needle"* ]]/ 2267 esac 2268 [[ $flag_icase ]] && shopt -u nocasematch 2269 2270 if [[ ! $flag_cycled && :$opts: == *:cyclic:* ]]; then 2271 if ((has_backward)); then 2272 ((index=${#_ble_history_edit[@]}-1)) 2273 ((index>start)) || return 1 2274 else 2275 ((index=0)) 2276 ((index<start)) || return 1 2277 fi 2278 else 2279 return 1 2280 fi 2281 done 2282 } 2283 function ble/history/isearch-forward { 2284 ble/history/isearch-forward.impl "$1" 2285 } 2286 function ble/history/isearch-backward { 2287 ble/history/isearch-forward.impl "$1:backward" 2288 }