Repository: shellgei/rusty_bash Branch: main Commit: 6b912371a8b2 Files: 191 Total size: 981.8 KB Directory structure: gitextract_3bjkuqsu/ ├── .github/ │ └── workflows/ │ ├── macos.yml │ └── ubuntu.yml ├── .gitignore ├── .sushrc ├── Cargo.toml ├── LICENSE ├── README.md ├── RELEASE.md ├── build.rs ├── docs/ │ └── SETUP_BASH_COMPLETION.md ├── error ├── i18n/ │ ├── ar.ftl │ ├── da.ftl │ ├── de.ftl │ ├── el.ftl │ ├── en.ftl │ ├── es.ftl │ ├── fi.ftl │ ├── fr.ftl │ ├── hi.ftl │ ├── it.ftl │ ├── ja.ftl │ ├── ko.ftl │ ├── nl.ftl │ ├── no.ftl │ ├── pl.ftl │ ├── pt.ftl │ ├── ru.ftl │ ├── sl.ftl │ ├── sv.ftl │ ├── sw.ftl │ ├── uk.ftl │ └── zh.ftl ├── src/ │ ├── core/ │ │ ├── builtins/ │ │ │ ├── alias.rs │ │ │ ├── caller.rs │ │ │ ├── cd.rs │ │ │ ├── command.rs │ │ │ ├── compgen.rs │ │ │ ├── complete.rs │ │ │ ├── compopt.rs │ │ │ ├── echo.rs │ │ │ ├── exec.rs │ │ │ ├── getopts.rs │ │ │ ├── hash.rs │ │ │ ├── history.rs │ │ │ ├── job_commands.rs │ │ │ ├── loop_control.rs │ │ │ ├── option.rs │ │ │ ├── printf.rs │ │ │ ├── pwd.rs │ │ │ ├── read.rs │ │ │ ├── source.rs │ │ │ ├── trap.rs │ │ │ ├── type_.rs │ │ │ ├── ulimit.rs │ │ │ ├── unset.rs │ │ │ ├── variable/ │ │ │ │ ├── print.rs │ │ │ │ └── set_value.rs │ │ │ └── variable.rs │ │ ├── builtins.rs │ │ ├── completion.rs │ │ ├── database/ │ │ │ ├── data/ │ │ │ │ ├── array.rs │ │ │ │ ├── array_int.rs │ │ │ │ ├── array_ondemand.rs │ │ │ │ ├── assoc.rs │ │ │ │ ├── assoc_int.rs │ │ │ │ ├── random.rs │ │ │ │ ├── seconds.rs │ │ │ │ ├── single.rs │ │ │ │ ├── single_int.rs │ │ │ │ ├── single_ondemand.rs │ │ │ │ ├── srandom.rs │ │ │ │ └── uninit.rs │ │ │ ├── data.rs │ │ │ ├── database_appenders.rs │ │ │ ├── database_checkers.rs │ │ │ ├── database_getters.rs │ │ │ ├── database_initializers.rs │ │ │ ├── database_print.rs │ │ │ ├── database_setters/ │ │ │ │ └── database_setter_backend.rs │ │ │ ├── database_setters.rs │ │ │ └── database_unsetters.rs │ │ ├── database.rs │ │ ├── file_descs.rs │ │ ├── history.rs │ │ ├── jobtable.rs │ │ └── options.rs │ ├── core.rs │ ├── elements/ │ │ ├── ansi_c_str.rs │ │ ├── command/ │ │ │ ├── arithmetic.rs │ │ │ ├── brace.rs │ │ │ ├── case.rs │ │ │ ├── coproc.rs │ │ │ ├── for.rs │ │ │ ├── function_def.rs │ │ │ ├── if.rs │ │ │ ├── paren.rs │ │ │ ├── repeat.rs │ │ │ ├── simple/ │ │ │ │ ├── alias.rs │ │ │ │ ├── hash.rs │ │ │ │ ├── parser.rs │ │ │ │ └── run_internal.rs │ │ │ ├── simple.rs │ │ │ ├── test.rs │ │ │ └── while.rs │ │ ├── command.rs │ │ ├── expr/ │ │ │ ├── arithmetic/ │ │ │ │ ├── calculator.rs │ │ │ │ ├── elem/ │ │ │ │ │ ├── float.rs │ │ │ │ │ ├── int.rs │ │ │ │ │ ├── ternary.rs │ │ │ │ │ └── variable.rs │ │ │ │ ├── elem.rs │ │ │ │ ├── parser.rs │ │ │ │ └── rev_polish.rs │ │ │ ├── arithmetic.rs │ │ │ ├── conditional/ │ │ │ │ ├── elem.rs │ │ │ │ └── parser.rs │ │ │ └── conditional.rs │ │ ├── expr.rs │ │ ├── io/ │ │ │ ├── pipe.rs │ │ │ └── redirect.rs │ │ ├── io.rs │ │ ├── job.rs │ │ ├── pipeline.rs │ │ ├── script.rs │ │ ├── substitution/ │ │ │ ├── array.rs │ │ │ ├── subscript.rs │ │ │ ├── value.rs │ │ │ └── variable.rs │ │ ├── substitution.rs │ │ ├── subword/ │ │ │ ├── ansi_c_quoted.rs │ │ │ ├── arithmetic.rs │ │ │ ├── braced_param/ │ │ │ │ ├── optional_operation/ │ │ │ │ │ ├── case_conv.rs │ │ │ │ │ ├── escape.rs │ │ │ │ │ ├── remove.rs │ │ │ │ │ ├── replace.rs │ │ │ │ │ ├── substr.rs │ │ │ │ │ └── value_check.rs │ │ │ │ ├── optional_operation.rs │ │ │ │ └── parse.rs │ │ │ ├── braced_param.rs │ │ │ ├── command_sub.rs │ │ │ ├── double_quoted.rs │ │ │ ├── escaped_char.rs │ │ │ ├── ext_glob.rs │ │ │ ├── file_input.rs │ │ │ ├── filler.rs │ │ │ ├── parameter.rs │ │ │ ├── paren.rs │ │ │ ├── process_sub.rs │ │ │ ├── simple.rs │ │ │ ├── single_quoted.rs │ │ │ └── varname.rs │ │ ├── subword.rs │ │ ├── word/ │ │ │ ├── brace_expansion.rs │ │ │ ├── path_expansion.rs │ │ │ ├── split.rs │ │ │ ├── substitution.rs │ │ │ └── tilde_expansion.rs │ │ └── word.rs │ ├── elements.rs │ ├── error/ │ │ ├── arith.rs │ │ ├── exec.rs │ │ ├── input.rs │ │ └── parse.rs │ ├── error.rs │ ├── feeder/ │ │ ├── scanner.rs │ │ ├── terminal/ │ │ │ ├── completion.rs │ │ │ └── key.rs │ │ └── terminal.rs │ ├── feeder.rs │ ├── i18n.rs │ ├── main.rs │ ├── main_c_option.rs │ ├── proc_ctrl.rs │ ├── signal.rs │ ├── utils/ │ │ ├── arg.rs │ │ ├── c_string.rs │ │ ├── clock.rs │ │ ├── directory.rs │ │ ├── exit.rs │ │ ├── file.rs │ │ ├── file_check.rs │ │ ├── glob/ │ │ │ ├── comparator.rs │ │ │ ├── extglob.rs │ │ │ └── parser.rs │ │ ├── glob.rs │ │ ├── restricted_shell.rs │ │ └── splitter.rs │ └── utils.rs └── test/ └── README ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/macos.yml ================================================ name: macos-latest on: push: branches: [ main, alpha, beta, sd/* ] paths: ['src/**', 'test/**', '!README.md' ] pull_request: branches: [ main ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: macos-latest steps: - uses: actions/checkout@v3 - name: Bash tests run: | dir=$PWD git clone https://github.com/shellgei/rusty_bash_test -b v1.2.7 --depth 1 #git clone https://github.com/shellgei/rusty_bash_test --depth 1 cd rusty_bash_test ./test.bash $dir ================================================ FILE: .github/workflows/ubuntu.yml ================================================ name: ubuntu-latest on: push: branches: [ main, alpha, beta, sd/* ] paths: ['src/**', 'test/**', '!README.md' ] pull_request: branches: [ main ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Bash tests run: | dir=$PWD git clone https://github.com/shellgei/rusty_bash_test -b v1.2.7 --depth 1 cd rusty_bash_test ./test.bash $dir ================================================ FILE: .gitignore ================================================ # Generated by Cargo # will have compiled files and executables /target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk test/ok test/error ================================================ FILE: .sushrc ================================================ case $- in *i*) ;; *) return;; esac case "$TERM" in xterm-color|*-256color) color_prompt=yes;; esac build_profile=$([[ "$SUSH_VERSION" == *-release ]] || echo "(${SUSH_VERSION##*-})") if [ "$color_prompt" = yes ]; then PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;36m\]\b\[\033[00m\]\[\033[01;35m\]\w\[\033[00m\]'$build_profile'🍣 ' else PS1='\u@\h:\w'$build_profile'🍣 ' fi case "$TERM" in xterm*|rxvt*) PS1="\[\033]2;\u@\h: \w\007\]$PS1" ;; *) ;; esac PS2='> ' PS4='+ ' alias ll='ls -l' alias git-writing='git add -A ; git commit -m Writing ; git push' command_not_found_handle() { #command_not_found should be loaded before bash-completion in this stage if [ -e /usr/lib/command-not-found ] ; then /usr/lib/command-not-found -- "$1" fi } export BASH_COMPLETION=/opt/homebrew/Cellar/bash-completion/1.3_3/etc/bash_completion if [ "$(uname)" = "Darwin" -a -f /opt/homebrew/Cellar/bash-completion/1.3_3/etc/bash_completion ]; then source /opt/homebrew/Cellar/bash-completion/1.3_3/etc/bash_completion complete -d cd elif [ -f /usr/share/bash-completion/bash_completion ]; then . /usr/share/bash-completion/bash_completion _comp_complete_load scp #for completion of rsync # . /usr/share/bash-completion/completions/git # for git-completion on WSL complete -d cd elif [ -f /etc/bash_completion ]; then . /etc/bash_completion complete -d cd fi ================================================ FILE: Cargo.toml ================================================ [package] name = "sush" version = "1.2.7" edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [package.metadata.compat] target_bash_version = "5.2.37" # Ubuntu 25.04 [dependencies] nix = { version = "0.30.1", features = ["fs", "process", "signal", "term", "user", "time", "hostname", "resource"]} termion = "4.0.5" unicode-width = "0.1.11" signal-hook = "0.3.17" rev_lines = "0.3.0" faccess = "0.2.4" io-streams = "0.16.3" regex = "1.11.1" rand = "0.9" rand_chacha = { version = "0.9.0", features = [ "os_rng" ]} time = "0.3" sprintf = "0.4" libc = "0.2.178" # Internationalization fluent-bundle = "0.16" unic-langid = "0.9" once_cell = "1" locale_config = "0.3" # Compile-time feature [features] lang_ar = [] lang_da = [] lang_de = [] lang_el = [] lang_en = [] lang_es = [] lang_fi = [] lang_fr = [] lang_hi = [] lang_it = [] lang_ja = [] lang_ko = [] lang_nl = [] lang_no = [] lang_pl = [] lang_pt = [] lang_ru = [] lang_sl = [] lang_sv = [] lang_sw = [] lang_uk = [] lang_zh = [] default = ["0", "1", "2", "3", "4", "5", "6"] # All 0 = ["lang_en", "lang_fr", "lang_ja"] # Dev Pack 1 = ["0", "lang_es", "lang_fr", "lang_it", "lang_pt"] # Latin 2 = ["0", "lang_nl", "lang_de", "lang_sl", "lang_el"] # Central Europe 3 = ["0", "lang_da", "lang_fi", "lang_no", "lang_sv"] # Northern Europe 4 = ["0", "lang_ru", "lang_uk", "lang_pl"] # Eastern Europe 5 = ["0", "lang_hi", "lang_ja", "lang_ko", "lang_zh"] # Asia 6 = ["0", "lang_ar", "lang_sw"] # Africa / Middle East [build-dependencies] cargo_toml = "0.22" [profile.release] opt-level = 3 codegen-units = 1 lto = true panic = "abort" ================================================ FILE: LICENSE ================================================ BSD 3-Clause License Copyright (c) 2024, Ryuichi Ueda All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ # Sushi shell (a.k.a. 🍣 Sush): a Bash clone shell implemented in Rust former name: Rusty Bash [![ubuntu-latest](https://github.com/shellgei/rusty_bash/actions/workflows/ubuntu.yml/badge.svg)](https://github.com/shellgei/rusty_bash/actions/workflows/ubuntu.yml) [![macos-latest](https://github.com/shellgei/rusty_bash/actions/workflows/macos.yml/badge.svg)](https://github.com/shellgei/rusty_bash/actions/workflows/macos.yml) ![](https://img.shields.io/github/license/shellgei/rusty_bash) ## NEWS Bash-completion starts working on our shell! ([how to use](docs/SETUP_BASH_COMPLETION.md)) ![completion](https://github.com/user-attachments/assets/e4af177c-3fdd-4f59-a70b-9c97df96b4bc) ## What's this? A clone of Bash, which is developed as a hobby of our group and for monthly articles on SoftwareDesign magazine published by Gijutsu-Hyohron Co., Ltd. ## Quick Start ```bash $ git clone https://github.com/shellgei/rusty_bash.git $ cd rusty_bash $ cargo run ・・・ Finished dev [unoptimized + debuginfo] target(s) in 0.04s Running `target/debug/sush` ueda@uedaP1g6:main🌵~/GIT/rusty_bash(debug)🍣 ``` ## Install ```bash $ git clone https://github.com/shellgei/rusty_bash.git $ cd rusty_bash $ cargo build --release ### ↓ Change /bin/ to /usr/local/bin/ or another path in $PATH if you are using Mac or BSD ### $ sudo cp target/release/sush /bin/ $ cp .sushrc ~/.sushrc # edit if some errors occur $ sush ueda@uedaP1g6:main🌵~/GIT/rusty_bash🍣 ``` ## Comparison with Bash 5.2 This graph shows the test result with the script in `./sush_test/bash_genuine_test` of [this test repository](https://github.com/ryuichiueda/bash_for_sush_test). Currently, the binary built from alpha repo has passed 27 of 84 test scripts. ![](https://github.com/ryuichiueda/bash_for_sush_test/blob/master/sush_test/graph.png) ### Bash behavior that we don't follow The following behavior of Bash will not be imitated by `sush`. So we alter the right output file (e.g `globstar.right`) for comparision. * the output order of associative array * output duplication of globstar * Bash outputs the same path repeatedly in some situations of globstar. It may be for compatibility of ksh. * overflow at calculations * Bash outputs overflow calculation results at the border of 64 bit intergers and `arith5.sub` tells that this behavior should be reproduced. But we don't follow it. ```bash ### Bash example ### $ echo $(( -9223372036854775808 * -1 )) -9223372036854775808 #IT'S WRONG. $ echo $(( -9223372036854775807 * -1 )) #IT'S OK. 9223372036854775807 ### Sushi shell ### 🍣 echo $(( -9223372036854775808 * -1 )) 9223372036854775808 🍣 echo $(( -9223372036854775807 * -1 )) 9223372036854775807 ``` * spaces of error log * Bash adds spaces to each token and displays them in error messages. These spaces are elliminated in our shell. ```bash ### Bash ### $ (( 1++ )) bash: ((: 1++ : syntax error: operand expected (error token is "+ ") ### Sush ### 🍣 (( 1++ )) sush: ((: 1++ : syntax error: operand expected (error token is "+") ``` * error messages of `readonly` * We added `readonly: ` to the messages in `attr.right`. * e.g.: `./attr.tests: line 17: a: readonly variable` -> `./attr.tests: line 17: readonly: a: readonly variable` ## Contribution Because the shell in this repository can be a standard one in the next generation, it may a good idea to leave your name as a contributor. Give us pull requests with what you think as contribution. As our community is not big, rules have not been fixed yet. Followings are not difficult but very important tasks. * To fix the code based on Clippy. (There are many warnings by Clippy in the current codes. ) * To develop builtin commands. (Especially `echo` may be easy. ) * To add test cases. * To fix the test methodology, especially for the parts related to human input. ### Important branch * alpha: checkout this branch if you want to develop. * beta: we are using the head version of this branch on a day-to-day basis. * main: the beta version is merged to this branch if fatal problems are not found for a week. * alpha-sushiline: a version with [sushiline](https://github.com/t-koba/sushline), which is a clone of GNU Readline ## List of Features * :heavy_check_mark: :available * :construction: :partially available (or having known bugs) * :no_good: : not implemented ### compound commands |features | status |features | status |features | status | |-------------------|----|-------------------|----|-------------------|----| | if | :heavy_check_mark: | while | :heavy_check_mark: | () | :heavy_check_mark: | | {} | :heavy_check_mark: | case | :heavy_check_mark: | until | :no_good: | select | :no_good: | | for | :heavy_check_mark: | [[ ]] | :heavy_check_mark: | ### special parameters |features | status |features | status |features | status | |-------------------|----|-------------------|----|-------------------|----| | $ | :heavy_check_mark: | ? | :heavy_check_mark: | * | :heavy_check_mark: | | @ | :heavy_check_mark: | # | :heavy_check_mark: | - | :heavy_check_mark: | | ! | :no_good: | _ | :heavy_check_mark: | ### builtin commands |features | status |features | status |features | status | |-------------------|----|-------------------|----|-------------------|----| | cd | :heavy_check_mark: | pwd | :heavy_check_mark: | read | :construction: | | exit | :heavy_check_mark: | source | :heavy_check_mark: | set | :construction: | | shopt | :construction: | : | :heavy_check_mark: | . | :heavy_check_mark: | [ | :no_good: | | alias | :heavy_check_mark: | bg | :construction: | bind | :no_good: | | break | :heavy_check_mark: | builtin | :heavy_check_mark: | caller | :under_construction: | | command | :heavy_check_mark: | compgen | :construction: | complete | :construction: | | compopt | :no_good: | continue | :heavy_check_mark: | declare | :no_good: | | dirs | :no_good: | disown | :heavy_check_mark: | echo | :no_good: | | enable | :no_good: | eval | :heavy_check_mark: | exec | :no_good: | | fc | :no_good: | fg | :construction: | getopts | :construction: | | hash | :no_good: | help | :no_good: | history | :construction: | | jobs | :construction: | kill | :under_construction: | let | :no_good: | | local | :heavy_check_mark: | logout | :no_good: | mapfile | :no_good: | | popd | :no_good: | printf | :heavy_check_mark: | pushd | :no_good: | | readonly | :no_good: | return | :heavy_check_mark: | false | :heavy_check_mark: | | shift | :heavy_check_mark: | suspend | :no_good: | test | :heavy_check_mark: | | times | :no_good: | trap | :no_good: | true | :heavy_check_mark: | | type | :no_good: | typeset | :no_good: | ulimit | :heavy_check_mark: | | umask | :no_good: | unalias | :heavy_check_mark: | unset | :construction: | | wait | :construction: | export | :heavy_check_mark: | ### options |features | status |features | status |features | status | |-------------------|----|-------------------|----|-------------------|----| | -c | :heavy_check_mark: | -i | :heavy_check_mark: | -l, --login | :no_good: | | -r | :no_good: | -s | :no_good: | -D | :no_good: | | [-+]O | :no_good: | -- | :no_good: | --debugger | :no_good: | | --dimp-po-strings | :no_good: | --help | :heavy_check_mark: | --init-file | :no_good: | | --rcfile | :no_good: | --noediting | :no_good: | --noprofile | :no_good: | | --norc | :no_good: | --posix | :under_construction: | --restricted | :heavy_check_mark: | | -v, --verbose | :no_good: | --version | :heavy_check_mark: | -e | :heavy_check_mark: | | --pipefail | :heavy_check_mark: | -B | :heavy_check_mark: | | | ### shopt |features | status |features | status |features | status | |-------------------|----|-------------------|----|-------------------|----| | autocd | :no_good: | cdable_vars | :no_good: | cdspell | :no_good: | | checkhash | :no_good: | checkjobs | :no_good: | checkwinsize | :no_good: | | cmdhist | :no_good: | compat31 | :no_good: | compat32 | :no_good: | | compat40 | :no_good: | compat41 | :no_good: | dirspell | :no_good: | | dotglob | :heavy_check_mark: | execfail | :no_good: | expand_aliases | :no_good: | | extdebug | :no_good: | extglob | :heavy_check_mark: | extquote | :no_good: | | failglob | :no_good: | force_fignore | :no_good: | globstar | :heavy_check_mark: | | gnu_errfmt | :no_good: | histappend | :no_good: | histreedit | :no_good: | | histverify | :no_good: | hostcomplete | :no_good: | huponexit | :no_good: | | interactive_comments | :no_good: | lastpipe | :no_good: | lithist | :no_good: | | login_shell | :no_good: | mailwarn | :no_good: | no_empty_cmd_completion | :no_good: | | nocaseglob | :no_good: | nocasematch | :no_good: | nullglob | :heavy_check_mark: | | progcomp | :heavy_check_mark: | promptvars | :no_good: | restricted_shell | :heavy_check_mark: | | shift_verbose | :no_good: | sourcepath | :no_good: | xpg_echo | :no_good: | ### variables Born Shell Variables |features | status |features | status |features | status | |-------------------|----|-------------------|----|-------------------|----| | CDPATH | :no_good: | HOME | :heavy_check_mark: | IFS | :heavy_check_mark: | | MAIL | :no_good: | MAILPATH | :no_good: | OPTARG | :heavy_check_mark: | | OPTIND | :heavy_check_mark: | PATH | :heavy_check_mark: | PS1 | :heavy_check_mark: | | PS2 | :heavy_check_mark: | | | | | Bash Variables |features | status |features | status |features | status | |-------------------|----|-------------------|----|-------------------|----| | _ | :heavy_check_mark: | BASH | :no_good: | BASHOPTS | :no_good: | | BASHPID | :heavy_check_mark: | BASH_ALIASES | :no_good: | BASH_ARGC | :no_good: | | BASH_ARGV | :no_good: | BASH_ARGV0 | :no_good: | BASH_CMDS | :no_good: | | BASH_COMMAND | :no_good: | BASH_COMPAT | :no_good: | BASH_ENV | :no_good: | | BASH_EXECUTION_STRING | :no_good: | BASH_LINENO | :no_good: | BASH_LOADABLES_PATH | :no_good: | | BASH_REMATCH | :heavy_check_mark: | BASH_SOURCE | :no_good: | BASH_SUBSHELL | :heavy_check_mark: | | BASH_VERSINFO | :heavy_check_mark: | BASH_VERSION | :heavy_check_mark: | BASH_XTRACEFD | :no_good: | | CHILD_MAX | :no_good: | COLUMNS | :no_good: | COMP_CWORD | :no_good: | | COMP_LINE | :no_good: | COMP_POINT | :no_good: | COMP_TYPE | :no_good: | | COMP_KEY | :no_good: | COMP_WORDBREAKS | :no_good: | COMP_WORDS | :no_good: | | COMPREPLY | :no_good: | COPROC | :no_good: | DIRSTACK | :no_good: | | EMACS | :no_good: | ENV | :no_good: | EPOCHREALTIME | :heavy_check_mark: | | EPOCHSECONDS | :heavy_check_mark: | EUID | :no_good: | EXECIGNORE | :no_good: | | FCEDIT | :no_good: | FIGNORE | :no_good: | FUNCNAME | :heavy_check_mark: | | FUNCNEST | :no_good: | GLOBIGNORE | :no_good: | GROUPS | :no_good: | | histchars | :no_good: | HISTCMD | :no_good: | HISTCONTROL | :no_good: | | HISTFILE | :heavy_check_mark: | HISTFILESIZE | :heavy_check_mark: | HISTIGNORE | :no_good: | | HISTSIZE | :no_good: | HISTTIMEFORMAT | :no_good: | HOSTFILE | :no_good: | | HOSTNAME | :no_good: | HOSTTYPE | :heavy_check_mark: | IGNOREEOF | :no_good: | | INPUTRC | :no_good: | INSIDE_EMACS | :no_good: | LANG | :heavy_check_mark: | | LC_ALL | :no_good: | LC_COLLATE | :no_good: | LC_CTYPE | :no_good: | | LC_MESSAGES | :no_good: | LC_NUMERIC | :no_good: | LC_TIME | :no_good: | | LINENO | :heavy_check_mark: | LINES | :no_good: | MACHTYPE | :heavy_check_mark: | | MAILCHECK | :no_good: | MAPFILE | :no_good: | OLDPWD | :heavy_check_mark: | | OPTERR | :no_good: | OSTYPE | :heavy_check_mark: | PIPESTATUS | :heavy_check_mark: | | POSIXLY_CORRECT | :no_good: | PPID | :no_good: | PROMPT_COMMAND | :no_good: | | PROMPT_DIRTRIM | :no_good: | PS0 | :no_good: | PS3 | :no_good: | | PS4 | :heavy_check_mark: | PWD | :heavy_check_mark: | RANDOM | :heavy_check_mark: | | READLINE_ARGUMENT | :no_good: | READLINE_LINE | :no_good: | READLINE_MARK | :no_good: | | READLINE_POINT | :no_good: | REPLY | :no_good: | SECONDS | :heavy_check_mark: | | SHELL | :heavy_check_mark: | SHELLOPTS | :no_good: | SHLVL | :heavy_check_mark: | | SRANDOM | :heavy_check_mark: | TIMEFORMAT | :no_good: | TMOUT | :no_good: | | TMPDIR | :no_good: | UID | :no_good: | | | ### beyond Bash |features | status | |-------------------|----| | repeat command | :heavy_check_mark: | | branch display in prompt | :heavy_check_mark: | ## Thanks to Partially in Japanese. * blog articles * [Rustでシェル作った | κeenのHappy Hacκing Blog](https://keens.github.io/blog/2016/09/04/rustdeshierutsukutta/) * [Rustで始める自作シェル その1 | ぶていのログでぶログ](https://tech.buty4649.net/entry/2021/12/19/235124) * [Rustのターミナル操作crateいろいろ | meganehouser](https://meganehouser.github.io/2019-12-11_rust-terminal-crates.html) * [原理原則で理解するフォアグラウンドプロセスとバックグラウンドプロセスの違い | @tajima_taso](https://qiita.com/tajima_taso/items/c5553762af5e1a599fed) * [Bashタブ補完自作入門 | Cybouzu Inside Out](https://blog.cybozu.io/entry/2016/09/26/080000) ## Attempts by other groups - [reubeno/brush](https://github.com/reubeno/brush) ## Copyright © 2022-2025 shellgei group - Ryuichi Ueda: [@ry@mi.shellgei.org](https://mi.shellgei.org/@ru), @ueda.tech (https://bsky.app/profile/ueda.tech) - [@caro@mi.shellgei.org](https://mi.shellgei.org/@caro) ================================================ FILE: RELEASE.md ================================================ See https://github.com/shellgei/rusty_bash/releases ================================================ FILE: build.rs ================================================ //SPDX-FileCopyrightText: 2024 @caro@mi.shellgei.org //SPDX-License-Identifier: BSD-3-Clause use cargo_toml::Manifest; use std::{env, path::PathBuf}; fn main() { // SUSH_VERSION, SUSH_VERSINFO[4] let profile = env::var("PROFILE").unwrap_or("".to_string()); println!("cargo:rustc-env=CARGO_BUILD_PROFILE={profile}"); // HOSTTYPE, MACHTYPE, BASH_VERSINFO[5], SUSH_VERSINFO[5] let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or("unknown".to_string()); println!("cargo:rustc-env=CARGO_CFG_TARGET_ARCH={target_arch}"); // MACHTYPE, BASH_VERSINFO[5], SUSH_VERSINFO[5] let target_vendor = env::var("CARGO_CFG_TARGET_VENDOR").unwrap_or("unknown".to_string()); println!("cargo:rustc-env=CARGO_CFG_TARGET_VENDOR={target_vendor}"); // OSTYPE, MACHTYPE, BASH_VERSINFO[5], SUSH_VERSINFO[5] let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or("unknown".to_string()); println!("cargo:rustc-env=CARGO_CFG_TARGET_OS={target_os}"); // metadata let manifest_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("Cargo.toml"); let manifest = Manifest::from_path(&manifest_path).expect("failed to parse Cargo.toml"); // compat let compat = manifest .package .as_ref() .and_then(|p| p.metadata.as_ref()?.get("compat")) .and_then(|v| v.as_table()) .expect("Missing [package.metadata.compat]"); for (k, v) in compat { let env_key: String = k .chars() .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }) .collect::() .to_ascii_uppercase(); let val = v .as_str() .unwrap_or_else(|| panic!("Non-string value for key {k} in [package.metadata.compat]")); println!("cargo:rustc-env=COMPAT_{env_key}={val}"); } } ================================================ FILE: docs/SETUP_BASH_COMPLETION.md ================================================ # setup of bash-completion - 20250427 Ryuichi Ueda - bluesky: @ueda.tech (https://bsky.app/profile/ueda.tech) ## contents - Linux - with command_not_found - macOS ## Linux (Ubuntu 24.04 or some versions near 24.04) The following three lines are minimally required for bash-completion at the bottom of `~/.sushrc` if you are using v1.1.4 or a version near v1.1.4. ```bash source /usr/share/bash-completion/bash_completion _comp_complete_load scp #for completion of rsync complete -d cd ``` With this three lines, you can use completion for various commands including `git`. The first line calls the main script of Bash-completion, which usually exists in `/usr/share/bash-completion/`. If you can't find the path, please search it by `find` and change the path. The second line is required since a warning disturbs completion for `rsync`. This problem is solved if the completion function for `scp` is load. Since this happens owing to a bug of Rusty Bash, it should be removed someday. Someday... The third line is also necessary to avoid a problem. This line resets the completion method for `cd` to directory completion since it was set to function completion after `bash_completion` for some reason. ### with `command_not_found` You can also use `command_not_found`. Before the three lines for bash-completion, please add the following function. The path should be changed if `command-not-found` script exists in the different directory. ```bash command_not_found_handle() { if [ -e /usr/lib/command-not-found ] ; then /usr/lib/command-not-found -- "$1" fi } ``` For some reason, this definition has to be written BEFORE the call of bash-completion. ## macOS (Sequoia 15.3.1 or some versions near it) In macOS, the three lines for bash-completion are required at the bottom of `~/.sushrc`. Moreover, we may have to install bash-completion by ourselves. When you are using homebrew, you can do it with the following command. ```bash 🍣 brew install bash-completion ``` Then you can find `bash_completion` script in a directory under `/opt/homebrew/Cellar` like this. ```bash 🍣 find /opt/homebrew/Cellar/ | grep bash_completion$ /opt/homebrew/Cellar//bash-completion/1.3_3/etc/bash_completion ``` Please use the path found as above instead of the path used in the Linux example. ================================================ FILE: error ================================================ ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_builtins.bash ./test_builtins.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_job.bash ./test_job.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_builtins.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_redirects.bash ./test_others.bash ./test_fixed_v1.2.1.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed.bash ./test_fixed_v1.2.4.bash ./test_fixed_v1.2.4.bash ./test_fixed_v1.2.4.bash ./test_fixed_v1.2.4.bash ./test_fixed_v1.2.4.bash ./test_fixed_v1.2.4.bash ./test_fixed_v1.2.4.bash ================================================ FILE: i18n/ar.ftl ================================================ license = ترخيص version = إصدار usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = هذا برنامج مفتوح المصدر. يمكنك استخدامه وتعديله وإعادة توزيعه بحرية، سواء بالشيفرة المصدرية أو بشكل ثنائي، مع أو بدون تعديل، بشرط الحفاظ على إشعار حقوق النشر الأصلي وقائمة الشروط وإخلاء المسؤولية. يتم توفير هذا البرنامج "كما هو"، دون أي ضمان من أي نوع، سواء كان صريحًا أو ضمنيًا، في حدود ما يسمح به القانون. ================================================ FILE: i18n/da.ftl ================================================ license = Licens version = version usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported builtins = Builtin commands: cd Change the current directory pwd Print the current working directory exit Exit the shell source Read and execute commands from a file : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases break Exit from a loop builtin Execute a shell builtin, bypassing functions command Execute a command, ignoring shell functions continue Resume the next iteration of a loop eval Evaluate arguments as a shell command local Declare local variables inside functions return Return from a shell function false Do nothing, unsuccessfully true Do nothing, successfully shift Shift positional parameters unalias Remove aliases shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = Dette er open source-software. Du er fri til at bruge, ændre og redistribuere denne software i kilde- eller binær form, med eller uden ændringer, forudsat at den originale copyrightmeddelelse, betingelser og ansvarsfraskrivelse bevares. DENNE SOFTWARE LEVERES "SOM DEN ER", UDEN NOGEN FORM FOR GARANTI, UDTRYKKELIG ELLER UNDERFORSTÅET, I DET OMFANG LOVEN TILLADER. ================================================ FILE: i18n/de.ftl ================================================ license = Lizenz version = Version usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = Dies ist Open-Source-Software. Sie dürfen diese Software in Quell- oder Binärform frei verwenden, modifizieren und weiterverbreiten, mit oder ohne Änderungen, vorausgesetzt, dass der ursprüngliche Urheberrechtshinweis, die Liste der Bedingungen und der Haftungsausschluss erhalten bleiben. DIESE SOFTWARE WIRD "WIE BESEHEN" BEREITGESTELLT, OHNE JEGLICHE GARANTIE, AUSDRÜCKLICH ODER STILLSCHWEIGEND, SOWEIT GESETZLICH ZULÄSSIG. ================================================ FILE: i18n/el.ftl ================================================ license = Άδεια version = έκδοση usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = Αυτό είναι λογισμικό ανοιχτού κώδικα. Είστε ελεύθεροι να χρησιμοποιείτε, να τροποποιείτε και να διανέμετε αυτό το λογισμικό σε μορφή πηγαίου ή δυαδικού κώδικα, με ή χωρίς τροποποιήσεις, υπό την προϋπόθεση ότι διατηρείται η αρχική ειδοποίηση πνευματικών δικαιωμάτων, ο κατάλογος όρων και η αποποίηση ευθυνών. ΑΥΤΟ ΤΟ ΛΟΓΙΣΜΙΚΟ ΠΑΡΕΧΕΤΑΙ "ΩΣ ΕΧΕΙ", ΧΩΡΙΣ ΚΑΜΙΑ ΕΓΓΥΗΣΗ, ΡΗΤΗ Ή ΣΙΩΠΗΡΗ, ΣΤΟΝ ΒΑΘΜΟ ΠΟΥ ΕΠΙΤΡΕΠΕΤΑΙ ΑΠΟ ΤΟΝ ΝΟΜΟ. ================================================ FILE: i18n/en.ftl ================================================ license = License version = version usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = This is open source software. You are free to use, modify, and redistribute this software in source or binary form, with or without modification, provided that the original copyright notice, list of conditions, and disclaimer are retained. THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, TO THE EXTENT PERMITTED BY LAW. ================================================ FILE: i18n/es.ftl ================================================ license = Licencia version = versión usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = Este es un software de código abierto. Usted es libre de usar, modificar y redistribuir este software en forma fuente o binaria, con o sin modificaciones, siempre que el aviso de copyright original, la lista de condiciones y la cláusula de exención de responsabilidad sean conservados. ESTE SOFTWARE SE PROPORCIONA "TAL CUAL", SIN GARANTÍA DE NINGÚN TIPO, EXPRESA O IMPLÍCITA, EN LA MEDIDA EN QUE LO PERMITA LA LEY. ================================================ FILE: i18n/fi.ftl ================================================ license = Lisenssi version = versio usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = Tämä on avoimen lähdekoodin ohjelmisto. Voit vapaasti käyttää, muokata ja levittää tätä ohjelmistoa lähde- tai binäärimuodossa, muutoksilla tai ilman, kunhan alkuperäinen tekijänoikeusilmoitus, ehdot ja vastuuvapauslauseke säilyvät. TÄMÄ OHJELMISTO TOIMITETAAN "SELLAISENAAN" ILMAN MITÄÄN TAKUITA, ILMAISTUJA TAI OLETETTUJA, LAIN SALLIMISSA RAJOISSA. ================================================ FILE: i18n/fr.ftl ================================================ license = Licence version = version usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = Ceci est un logiciel open source. Vous êtes libre d'utiliser, de modifier et de redistribuer ce logiciel sous forme source ou binaire, avec ou sans modification, à condition que l'avis de droit d’auteur original, la liste des conditions et la clause de non-responsabilité soient conservés. CE LOGICIEL EST FOURNI "TEL QUEL", SANS GARANTIE D'AUCUNE SORTE, EXPRESSE OU IMPLICITE, DANS LA LIMITE AUTORISÉE PAR LA LOI. ================================================ FILE: i18n/hi.ftl ================================================ license = लाइसेंस version = संस्करण usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = यह एक ओपन सोर्स सॉफ़्टवेयर है। आप इस सॉफ़्टवेयर का उपयोग, संशोधन और पुनर्वितरण स्वतंत्र रूप से कर सकते हैं, स्रोत या बाइनरी रूप में, संशोधन के साथ या बिना, बशर्ते कि मूल कॉपीराइट सूचना, शर्तों की सूची, और अस्वीकरण बनाए रखें। यह सॉफ़्टवेयर "जैसा है" प्रदान किया गया है, किसी भी प्रकार की वारंटी के बिना, स्पष्ट या निहित, कानून द्वारा अनुमत सीमा तक। ================================================ FILE: i18n/it.ftl ================================================ License = Licenza version = versione usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = Questo è un software open source. Sei libero di usare, modificare e ridistribuire questo software in forma sorgente o binaria, con o senza modifiche, a condizione che l'avviso di copyright originale, l'elenco delle condizioni e la clausola di esclusione di responsabilità siano mantenuti. QUESTO SOFTWARE È FORNITO "COSÌ COM'È", SENZA ALCUNA GARANZIA, ESPRESSA O IMPLICITA, NEI LIMITI CONSENTITI DALLA LEGGE. ================================================ FILE: i18n/ja.ftl ================================================ license = ライセンス version = バージョン usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = これはオープンソースソフトウェアです。 このソフトウェアは、オリジナルの著作権表示、条件の一覧、 免責事項が保持されている限り、ソースまたはバイナリ形式で、 修正の有無にかかわらず、自由に使用、変更、再配布できます。 本ソフトウェアは、法律で許される範囲において、 明示的または黙示的ないかなる保証もなく「現状のまま」提供されます。 ================================================ FILE: i18n/ko.ftl ================================================ License = 라이선스 version = 버전 usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = 이것은 오픈 소스 소프트웨어입니다. 원본 저작권 고지, 조건 목록 및 면책 조항이 유지되는 한, 이 소프트웨어를 소스 또는 바이너리 형태로 수정하거나 하지 않고도 자유롭게 사용, 수정 및 재배포할 수 있습니다. 이 소프트웨어는 법이 허용하는 범위 내에서 명시적이거나 묵시적인 어떠한 보증 없이 "있는 그대로" 제공됩니다. ================================================ FILE: i18n/nl.ftl ================================================ license = Licentie version = versie usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = Dit is open-sourcesoftware. U bent vrij om deze software te gebruiken, aan te passen en opnieuw te verspreiden, in bron- of binaire vorm, met of zonder wijzigingen, op voorwaarde dat de originele copyrightvermelding, lijst van voorwaarden en disclaimer behouden blijven. DEZE SOFTWARE WORDT GELEVERD "AS IS", ZONDER ENIGE GARANTIE, EXPLICIET OF IMPLICIET, VOOR ZOVER TOEGESTAAN DOOR DE WET. ================================================ FILE: i18n/no.ftl ================================================ license = Lisens version = versjon usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = Dette er åpen kildekode-programvare. Du står fritt til å bruke, endre og redistribuere denne programvaren, i kildekode eller binær form, med eller uten modifikasjoner, forutsatt at den opprinnelige opphavsrettsmerknaden, vilkårene og ansvarsfraskrivelsen beholdes. DENNE PROGRAMVAREN LEVERES "SOM DEN ER", UTEN GARANTIER AV NOE SLAG, VERKEN UTTRYKTE ELLER UNDERFORSTÅTTE, I DEN UTSTREKNING LOVEN TILLATER. ================================================ FILE: i18n/pl.ftl ================================================ license = Licencja version = wersja usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = To jest oprogramowanie open source. Możesz swobodnie używać, modyfikować i rozpowszechniać to oprogramowanie w formie źródłowej lub binarnej, z modyfikacjami lub bez, pod warunkiem zachowania oryginalnej noty copyright, listy warunków i zrzeczenia się odpowiedzialności. TO OPROGRAMOWANIE JEST DOSTARCZANE "TAKIE, JAKIE JEST", BEZ ŻADNEJ GWARANCJI, WYRAŹNEJ LUB DOROZUMIANEJ, W ZAKRESIE DOZWOLONYM PRZEZ PRAWO. ================================================ FILE: i18n/pt.ftl ================================================ license = Licença version = versão usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = Este é um software de código aberto. Você é livre para usar, modificar e redistribuir este software em forma de código-fonte ou binário, com ou sem modificações, desde que o aviso de direitos autorais, a lista de condições e a isenção de responsabilidade originais sejam mantidos. ESTE SOFTWARE É FORNECIDO "NO ESTADO EM QUE SE ENCONTRA", SEM QUALQUER GARANTIA, EXPRESSA OU IMPLÍCITA, NA MEDIDA PERMITIDA PELA LEI. ================================================ FILE: i18n/ru.ftl ================================================ license = Лицензия version = версия usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = Это программное обеспечение с открытым исходным кодом. Вы можете свободно использовать, изменять и распространять это программное обеспечение в исходной или двоичной форме, с изменениями или без, при условии сохранения оригинального уведомления об авторских правах, списка условий и отказа от ответственности. ЭТО ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ "КАК ЕСТЬ", БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, В ПРЕДЕЛАХ, ДОПУСКАЕМЫХ ЗАКОНОМ. ================================================ FILE: i18n/sl.ftl ================================================ license = Licenca version = različica usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = To je programska oprema z odprto kodo. Prosto jo lahko uporabljate, spreminjate in razširjate v izvorni ali binarni obliki, s spremembami ali brez, pod pogojem, da se ohranijo izvirno obvestilo o avtorskih pravicah, seznam pogojev in izjava o omejitvi odgovornosti. TA PROGRAMSKA OPREMA SE PONUDI "TAKŠNA, KOT JE", BREZ KAKRŠNE KOLI GARANCIJE, IZRECNE ALI IMPLICITNE, V OBSEGU, KI GA DOVOLJUJE ZAKON. ================================================ FILE: i18n/sv.ftl ================================================ license = Licens version = version usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = Detta är öppen källkodsprogramvara. Du är fri att använda, modifiera och distribuera denna programvara i källkod eller binär form, med eller utan ändringar, förutsatt att det ursprungliga upphovsrättsmeddelandet, villkoren och ansvarsfriskrivningen bevaras. DENNA PROGRAMVARA TILLHANDAHÅLLS "I BEFINTLIGT SKICK", UTAN NÅGON GARANTI, VARE SIG UTTRYCKLIG ELLER UNDERFÖRSTÅDD, I DEN UTSTRÄCKNING LAGEN MEDGER. ================================================ FILE: i18n/sw.ftl ================================================ license = Leseni version = toleo usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = Hii ni programu ya chanzo huria. Unaweza kuitumia, kuibadilisha, na kuisambaza tena kwa uhuru katika hali ya msimbo wa chanzo au iliyobainishwa, ukiwa umeifanyia mabadiliko au la, mradi taarifa ya hakimiliki, masharti na kanusho asilia zimedumishwa. PROGRAMU HII HUTOLEWA "KAMA ILIVYO", BILA DHAMANA YOYOTE, IWE IKO WAZI AU IMEFICHWA, KADRI INAVYORUHUSIWA KISHERIA. ================================================ FILE: i18n/uk.ftl ================================================ license = Ліцензія version = версія usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = Це програмне забезпечення з відкритим кодом. Ви можете вільно використовувати, змінювати та розповсюджувати це програмне забезпечення у вихідному або бінарному вигляді, з модифікаціями або без, за умови збереження оригінального повідомлення про авторські права, списку умов і відмови від відповідальності. ЦЕ ПРОГРАМНЕ ЗАБЕЗПЕЧЕННЯ НАДАЄТЬСЯ "ЯК Є", БЕЗ ЖОДНИХ ГАРАНТІЙ, ЯВНИХ ЧИ НЕЯВНИХ, У МЕЖАХ, ДОЗВОЛЕНИХ ЗАКОНОМ. ================================================ FILE: i18n/zh.ftl ================================================ license = 授權 version = 版本 usage = Usage: sushi [LONG OPTIONS] [OPTIONS] [SCRIPT] [ARGS] options = Options: -c Execute COMMAND and exit -i Force interactive mode -l, --login unsuported -r unsuported -s unsuported -D unsuported -O, +O unsuported -- unsuported --debugger unsuported --dimp-po-strings unsuported --help Display this help message and exit --init-file FILE unsuported --rcfile FILE unsuported --noediting unsuported --noprofile unsuported --norc unsuported --posix unsuported --restricted unsuported -v, --verbose unsuported --version Display version information and exit -e Exit immediately if a command returns non‑zero --pipefail Return status of first failing command in pipeline -B Enable brace expansion (equivalent to `set -B`) comp-commands = Compound commands: if Conditional execution while Loop while a condition is true () Run commands in a subshell case Match patterns against a word until unsupported for Iterate over a list of items builtins = Builtin commands: : No-op (does nothing) "." Source a file in the current shell alias Define or display aliases bg Resume a job in the background bind Unsupported break Exit from a loop builtin Execute a shell builtin, bypassing functions caller Unsupported cd Change the current directory command Execute a command, ignoring shell functions compgen Generate possible completion matches complete Specify how arguments are completed compopt Unsupported continue Resume the next iteration of a loop declare Unsupported dirs Unsupported disown Unsupported echo Unsupported enable Unsupported eval Evaluate arguments as a shell command exec Unsupported exit Exit the shell export Unsupported false Do nothing, unsuccessfully fc Unsupported fg Resume a job in the foreground getopts Parse positional parameters hash Unsupported help Unsupported history Show or manipulate the command history jobs Display status of jobs kill Unsupported let Unsupported local Declare local variables inside functions logout Unsupported mapfile Unsupported popd Unsupported printf Unsupported pushd Unsupported pwd Print the current working directory read Read a line from standard input readonly Unsupported return Return from a shell function set Modify shell options shift Shift positional parameters shopt Change shell optional behavior source Read and execute commands from a file suspend Unsupported test Unsupported times Unsupported trap Unsupported true Do nothing, successfully type Unsupported typeset Unsupported ulimit Unsupported umask Unsupported unalias Remove aliases unset Unset variables or functions wait Wait for jobs to complete parameters = Special parameters: "$" Process ID of the shell or script ? Exit status of the last command @ All positional parameters (as separate words) # Number of positional parameters - Current shell options _ Last argument of the previous command ! unsupported shopt = Shell options: dotglob Include hidden files (starting with .) in pathname expansions extglob Enable extended pattern matching operators progcomp Enable programmable command completion nullglob Allow patterns which match nothing to expand to null string variables-born = Born Shell Variables: CDPATH unsuported HOME User’s home directory IFS Internal Field Separator (partial support) MAIL unsuported MAILPATH unsuported OPTARG Argument value for the current option (getopts) OPTIND Index of the next argument to be processed by getopts PATH Search path for commands PS1 Primary prompt string PS2 Secondary prompt string variables-bash = Bash Variables: _ Last argument of the previous command BASH unsuported BASHOPTS unsuported BASHPID PID of the current Bash process BASH_ALIASES unsuported BASH_ARGC unsuported BASH_ARGV unsuported BASH_ARGV0 unsuported BASH_CMDS unsuported BASH_COMMAND unsuported BASH_COMPAT unsuported BASH_ENV unsuported BASH_EXECUTION_STRING unsuported BASH_LINENO unsuported BASH_LOADABLES_PATH unsuported BASH_REMATCH Array of regex capture groups BASH_SOURCE unsuported BASH_SUBSHELL Current subshell level BASH_VERSINFO Array with Bash version fields BASH_VERSION Human‑readable Bash version BASH_XTRACEFD unsuported CHILD_MAX unsuported COLUMNS unsuported COMP_CWORD unsuported COMP_LINE unsuported COMP_POINT unsuported COMP_TYPE unsuported COMP_KEY unsuported COMP_WORDBREAKS unsuported COMP_WORDS unsuported COMPREPLY unsuported COPROC unsuported DIRSTACK unsuported EMACS unsuported ENV unsuported EPOCHREALTIME Epoch seconds with microseconds EPOCHSECONDS Epoch seconds (integer) EUID unsuported EXECIGNORE unsuported FCEDIT unsuported FIGNORE unsuported FUNCNAME unsuported FUNCNEST unsuported GLOBIGNORE unsuported GROUPS unsuported histchars unsuported HISTCMD unsuported HISTCONTROL unsuported HISTFILE Path to the history file HISTFILESIZE Max lines kept in history file HISTIGNORE unsuported HISTSIZE unsuported HISTTIMEFORMAT unsuported HOSTFILE unsuported HOSTNAME unsuported HOSTTYPE Hardware platform string IGNOREEOF unsuported INPUTRC unsuported INSIDE_EMACS unsuported LANG Current locale LC_ALL unsuported LC_COLLATE unsuported LC_CTYPE unsuported LC_MESSAGES unsuported LC_NUMERIC unsuported LC_TIME unsuported LINENO Current script line number LINES unsuported MACHTYPE Machine type triple MAILCHECK unsuported MAPFILE unsuported OLDPWD Previous working directory OPTERR unsuported OSTYPE Operating‑system type PIPESTATUS Exit statuses of the last pipeline POSIXLY_CORRECT unsuported PPID unsuported PROMPT_COMMAND unsuported PROMPT_DIRTRIM unsuported PS0 unsuported PS3 unsuported PS4 Debug prompt (used with set -x) PWD Current working directory RANDOM Pseudo‑random integer (0‑32767) READLINE_ARGUMENT unsuported READLINE_LINE unsuported READLINE_MARK unsuported READLINE_POINT unsuported REPLY unsuported SECONDS Seconds since the shell started SHELL Path to the user’s default shell SHELLOPTS unsuported SHLVL Shell nesting level SRANDOM 64-bit cryptographic random TIMEFORMAT unsuported TMOUT unsuported TMPDIR unsuported UID unsuported Beyond Bash feature: branch display in prompt text-help = Project homepage: https://github.com/shellgei/rusty_bash text-version = 本軟體為開放原始碼軟體。 您可自由使用、修改及再散佈本軟體, 不論是原始碼或二進位形式,無論是否修改,前提是保留 原始著作權聲明、條款清單及免責聲明。 本軟體按「原樣」提供,無任何明示或默示保證, 在法律允許的範圍內適用。 ================================================ FILE: src/core/builtins/alias.rs ================================================ //SPDX-FileCopyrightText: 2024 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::ShellCore; pub fn alias(core: &mut ShellCore, args: &[String]) -> i32 { if args.len() == 1 || args.len() == 2 && args[1] == "-p" { if !core.shopts.query("expand_aliases") { return 0; } for k in &core.db.get_indexes_all("BASH_ALIASES") { let v = core.db.get_elem("BASH_ALIASES", k).unwrap(); println!("alias {k}='{v}'"); } return 0; } if args.len() == 2 && args[1].contains("=") { let kv: Vec = args[1].split('=').map(|t| t.to_string()).collect(); let _ = core .db .set_assoc_elem("BASH_ALIASES", &kv[0], &kv[1..].join("="), None); return 0; } if args.len() == 2 && core.db.has_array_value("BASH_ALIASES", &args[1]) { let alias = core.db.get_elem("BASH_ALIASES", &args[1]).unwrap(); println!("alias {}='{}'", &args[1], &alias); return 0; } 0 } pub fn unalias(core: &mut ShellCore, args: &[String]) -> i32 { if args.len() <= 1 { println!("unalias: usage: unalias [-a] name [name ...]"); } if args.iter().any(|s| s == "-a") { let _ = core.db.unset("BASH_ALIASES", None, false); let _ = core.db.init_assoc("BASH_ALIASES", None, true, false); return 0; } args[1..].iter().for_each(|e| { let _ = core.db.unset_array_elem("BASH_ALIASES", e); }); 0 } ================================================ FILE: src/core/builtins/caller.rs ================================================ //SPDX-FileCopyrightText: 2025 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::ShellCore; fn caller_no_arg(core: &mut ShellCore) -> i32 { let linenos_len = core.db.index_based_len("BASH_LINENO"); if linenos_len == 0 { return 1; } let lineno = core.db.get_elem("BASH_LINENO", "0").unwrap(); let mut funcname = core.db.get_elem("FUNCNAME", "1").unwrap(); if funcname == "" { funcname = "NULL".to_string(); }else { funcname = "main".to_string(); } println!("{} {}", &lineno, funcname); 0 } fn caller_arg(core: &mut ShellCore, args: &[String]) -> i32 { let pos = match args[1].parse::() { Ok(n) => n, _ => return 1, }; let linenos_len = core.db.index_based_len("BASH_LINENO"); if linenos_len == 0 { return 1; } //let functions_len = core.db.index_based_len("FUNCNAME"); let lineno = core.db.get_elem("BASH_LINENO", &pos.to_string()).unwrap(); let funcname = core.db.get_elem("FUNCNAME", &(pos+1).to_string()).unwrap(); let mut script_name = core.script_name.clone(); if script_name == "-" { script_name = "main".to_string(); } println!("{} {} {}", &lineno, funcname, script_name); 0 } pub fn caller(core: &mut ShellCore, args: &[String]) -> i32 { if args.len() == 1 { caller_no_arg(core) }else{ caller_arg(core, args) } } ================================================ FILE: src/core/builtins/cd.rs ================================================ //SPDX-FileCopyrightText: 2023 Ryuichi Ueda //SPDX-FileCopyrightText: 2023 @caro@mi.shellgei.org //SPDX-License-Identifier: BSD-3-Clause use crate::utils::{arg, file}; use crate::{error, ShellCore}; pub fn cd(core: &mut ShellCore, args: &[String]) -> i32 { if core.db.flags.contains('r') { return super::error_(1, &args[0], "restricted", core); } let mut args = args.to_owned(); arg::consume_arg("--", &mut args); if args.len() > 2 { eprintln!("sush: cd: too many arguments"); return 1; } // only "cd" if args.len() == 1 { set_oldpwd(core); let home = core.db.get_param("HOME").unwrap_or_default(); return change_directory(core, &home); } // cd - if args[1] == "-" { return cd_oldpwd(core); } // cd /some/dir set_oldpwd(core); change_directory(core, &args[1]) } fn cd_oldpwd(core: &mut ShellCore) -> i32 { match core.db.get_param("OLDPWD") { Ok(old) => { println!("{}", &old); set_oldpwd(core); change_directory(core, &old) } Err(_) => { error::print("cd: OLDPWD not set", core); 1 } } } fn set_oldpwd(core: &mut ShellCore) { if let Some(old) = core.get_current_directory() { let _ = core .db .set_param("OLDPWD", &old.display().to_string(), Some(0)); }; } fn change_directory(core: &mut ShellCore, target: &str) -> i32 { let path = file::make_canonical_path(core, target); if core.set_current_directory(&path).is_ok() { let _ = core .db .set_param("PWD", &path.display().to_string(), Some(0)); 0 } else { eprintln!("sush: cd: {:?}: No such file or directory", &path); 1 } } ================================================ FILE: src/core/builtins/command.rs ================================================ //SPDX-FileCopyrightText: 2025 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::elements::command::simple::SimpleCommand; use crate::elements::io::pipe::Pipe; use crate::utils::{arg, file}; use crate::{error, file_check, proc_ctrl, utils, ShellCore}; pub fn builtin(core: &mut ShellCore, args: &[String]) -> i32 { if args.len() <= 1 { return 0; } if !core.builtins.contains_key(&args[1]) { let msg = format!("{}: not a shell builtin", &args[1]); return super::error_(1, &args[0], &msg, core); } core.builtins[&args[1]](core, &args[1..]) } fn command_v(words: &[String], core: &mut ShellCore, large_v: bool) -> i32 { if words.is_empty() { return 0; } let mut return_value = 1; for com in words.iter() { if utils::reserved(com) { return_value = 0; match large_v { true => println!("{} is a shell keyword", &com), false => println!("{}", &com), } } else if core.db.has_array_value("BASH_ALIASES", com) { return_value = 0; let alias = core.db.get_elem("BASH_ALIASES", com).unwrap(); match large_v { true => println!("{} is aliased to `{}'", &com, &alias), false => println!("alias {}='{}'", &com, &alias), } } else if core.builtins.contains_key(com) { return_value = 0; match large_v { true => println!("{} is a shell builtin", &com), false => println!("{}", &com), } } else if core.db.functions.contains_key(com) { return_value = 0; match large_v { true => { println!("{} is a function", &com); core.db.functions.get_mut(com).unwrap().pretty_print(0); } false => println!("{}", &com), } } else if let Some(path) = file::search_command(com) { return_value = 0; match large_v { true => println!("{} is {}", &com, &path), false => println!("{}", &com), } } else if file_check::is_executable(com) { return_value = 0; match large_v { true => println!("{} is {}", &com, &com), false => println!("{}", &com), } } else if large_v { let msg = format!("command: {com}: not found"); error::print(&msg, core); } } return_value } pub fn command(core: &mut ShellCore, args: &[String]) -> i32 { if args.len() > 1 { if core.subst_builtins.contains_key(&args[1]) { //TODO return super::error_(1, &args[0], "substitution command are not supported", core); } } let mut args = arg::dissolve_options(args); if core.db.flags.contains('r') && arg::consume_arg("-p", &mut args) { return super::error_(1, &args[0], "-p: restricted", core); } if args.len() <= 1 { return 0; } let mut pos = 1; while args.len() > pos { match args[pos].starts_with("-") { true => pos += 1, false => break, } } let words = args[pos..].to_vec(); if words.is_empty() { return 0; } let mut args = args[..pos].to_vec(); args = arg::dissolve_options(&args); let last_option = args.last().unwrap(); if last_option == "-V" || last_option == "-v" { return command_v(&words, core, last_option == "-V"); } else if core.builtins.contains_key(&words[0]) { return core.builtins[&words[0]](core, &words[..]); } let mut command = SimpleCommand::default(); let mut pipe = Pipe::new("".to_string()); command.args = words; if let Ok(pid) = command.exec_command(core, &mut pipe) { proc_ctrl::wait_pipeline(core, vec![pid], false, false); } core.db.exit_status } ================================================ FILE: src/core/builtins/compgen.rs ================================================ //SPDX-FileCopyrightText: 2023 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::elements::word::{path_expansion, tilde_expansion}; use crate::elements::word::{Word, WordMode}; use crate::utils; use crate::utils::{arg, directory, glob}; use crate::{file_check, Feeder, ShellCore}; use faccess; use faccess::PathExt; use rev_lines::RevLines; use std::env; use std::collections::HashSet; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; pub fn compgen_f(core: &mut ShellCore, args: &[String], dir_only: bool) -> Vec { let mut arg_index = 2; if args.len() > 2 && args[2] == "--" { arg_index = 3; } let path = if arg_index < args.len() { args[arg_index].to_string() } else { "".to_string() } .replace("\\", ""); let mut split: Vec = path.split("/").map(|s| s.to_string()).collect(); let key = match split.pop() { Some(g) => g, _ => return vec![], }; split.push("".to_string()); let org_dir = split.join("/"); let mut dir = org_dir.clone(); if dir.starts_with("~") { let mut feeder = Feeder::new(&dir); if let Ok(Some(mut w)) = Word::parse(&mut feeder, core, Some(WordMode::CompgenF)) { tilde_expansion::eval(&mut w, core); dir = w.text + &feeder.consume(feeder.len()); } } if key.is_empty() { let mut files = directory::files(&dir); if dir_only { files.retain(|p| file_check::is_dir(&(dir.clone() + p))); } files.sort(); return files.iter().map(|f| org_dir.clone() + f).collect(); } let mut ans = directory::glob(&dir, &(key.clone() + "*"), &core.shopts); if key == "." { ans.append(&mut directory::glob(&dir, ".", &core.shopts)); ans.append(&mut directory::glob(&dir, "..", &core.shopts)); } ans.iter_mut().for_each(|a| { a.pop(); }); if dir_only { ans.retain(|p| file_check::is_dir(p)); } ans.sort(); ans.iter_mut().for_each(|e| { *e = e.replacen(&dir, &org_dir, 1); }); ans } fn normalize_compgen_args(args: &[String]) -> Vec { if args.len() < 3 || args[1] != "-A" { return args.to_vec(); } let mut result = args.to_vec(); result.remove(1); let replace = match result[1].as_str() { "command" => "-c", "directory" => "-d", "file" => "-f", "user" => "-u", "setopt" => "-o", "function" => "-A function", "hostname" => "-A hostname", "shopt" => "-A shopt", "stopped" => "-A stopped", "job" => "-j", "variable" => "-v", a => a, }; result[1] = replace.to_string(); result } fn command_list(target: &String, core: &mut ShellCore) -> Vec { let mut comlist = HashSet::new(); for path in core.db.get_param("PATH").unwrap_or_default().split(':') { /* /mnt is ellimimated from PATH on WSL because it contains all files in Windows.*/ let explorer_path = path.ends_with("Windows") || path.ends_with("WINDOWS"); if utils::is_wsl() && path.starts_with("/mnt") && !explorer_path { // We want to use explorer.exe continue; } for command in directory::files(path).iter() { if !Path::new(&(path.to_owned() + "/" + command)).executable() { continue; } if command.starts_with(target) { comlist.insert(command.clone()); } } } let mut ans: Vec = comlist.iter().map(|c| c.to_string()).collect(); ans.sort(); ans } pub fn compgen(core: &mut ShellCore, args: &[String]) -> i32 { let args = args.to_owned(); if args.len() <= 1 { eprintln!("sush: {}: still unsupported", &args[0]); return 1; } let mut args = arg::dissolve_options(&args); let exclude = arg::consume_with_next_arg("-X", &mut args); //TODO: implement X pattern let prefix = arg::consume_with_next_arg("-P", &mut args); let suffix = arg::consume_with_next_arg("-S", &mut args); let args = normalize_compgen_args(&args); let mut ans = match args[1].as_str() { "-a" => compgen_a(core, &args), "-b" => compgen_b(core, &args), "-c" => compgen_c(core, &args), "-d" => compgen_d(core, &args), "-e" => compgen_e(&args), "-f" => compgen_f(core, &args, false), "-h" => compgen_h(core, &args), //history (sush original) "-j" => compgen_j(core, &args), "-o" => compgen_o(core, &args), "-u" => compgen_u(core, &args), "-v" => compgen_v(core, &args), "-A function" => compgen_function(core, &args), "-A hostname" => compgen_hostname(core, &args), "-A shopt" => compgen_shopt(core, &args), "-A stopped" => compgen_stopped(core, &args), "-W" => { if args.len() < 2 { eprintln!("sush: compgen: -W: option requires an argument"); return 2; } compgen_large_w(core, &args) } "-G" => { if args.len() < 2 { eprintln!("sush: compgen: -G: option requires an argument"); return 2; } compgen_large_g(core, &args) } _ => { eprintln!("sush: compgen: {}: invalid option", &args[1]); return 2; } }; if let Some(pattern) = exclude { let extglob = core.shopts.query("extglob"); ans.retain(|a| !glob::parse_and_compare(a, &pattern, extglob)); } if let Some(p) = prefix { for a in ans.iter_mut() { *a = p.clone() + a; } } if let Some(s) = suffix { for a in ans.iter_mut() { *a = a.to_owned() + &s.clone(); } } ans.iter().for_each(|a| println!("{}", &a)); match ans.is_empty() { true => 1, false => 0, } } fn get_head(args: &[String], pos: usize) -> String { if args.len() > pos && args[pos] != "--" { args[pos].clone() } else if args.len() > pos + 1 { args[pos + 1].clone() } else { "".to_string() } } fn drop_unmatch(args: &[String], pos: usize, list: &mut Vec) { let head = get_head(args, pos); if !head.is_empty() { list.retain(|s| s.starts_with(&head)); } } pub fn compgen_a(core: &mut ShellCore, args: &[String]) -> Vec { let mut commands = vec![]; let mut aliases: Vec = core.db.get_indexes_all("BASH_ALIASES").to_vec(); commands.append(&mut aliases); let head = get_head(args, 2); if !head.is_empty() { commands.retain(|a: &String| a.starts_with(&head)); } commands } pub fn compgen_b(core: &mut ShellCore, args: &[String]) -> Vec { let mut commands = vec![]; let mut builtins: Vec = core.builtins.keys().cloned().collect(); commands.append(&mut builtins); let head = get_head(args, 2); if !head.is_empty() { commands.retain(|a| a.starts_with(&head)); } commands } pub fn compgen_c(core: &mut ShellCore, args: &[String]) -> Vec { let mut commands = vec![]; if args.len() > 2 { commands.extend(compgen_f(core, args, false)); } commands.retain(|p| Path::new(p).executable() || file_check::is_dir(p)); let mut aliases: Vec = core.db.get_indexes_all("BASH_ALIASES").to_vec(); commands.append(&mut aliases); let mut builtins: Vec = core.builtins.keys().cloned().collect(); commands.append(&mut builtins); let mut functions: Vec = core.db.functions.keys().cloned().collect(); commands.append(&mut functions); let head = get_head(args, 2); if !head.is_empty() { commands.retain(|a| a.starts_with(&head)); } let mut command_in_paths = command_list(&head, core); commands.append(&mut command_in_paths); commands } fn compgen_d(core: &mut ShellCore, args: &[String]) -> Vec { compgen_f(core, args, true) } pub fn compgen_e(args: &[String]) -> Vec { let mut envs = env::vars().map(|e| e.0).collect::>(); let head = get_head(args, 2); if !head.is_empty() { envs.retain(|a| a.starts_with(&head)); } envs } pub fn compgen_h(core: &mut ShellCore, _: &[String]) -> Vec { let len = core.history.len(); if len >= 10 { return core.history[0..10].to_vec(); } let mut ans = core.history.to_vec(); if let Ok(hist_file) = File::open(core.db.get_param("HISTFILE").unwrap_or_default()) { for h in RevLines::new(BufReader::new(hist_file)) { if let Ok(s) = h { ans.push(s) } if ans.len() >= 10 { return ans; } } } while ans.len() < 10 { ans.push("echo Hello World".to_string()); } ans } pub fn compgen_v(core: &mut ShellCore, args: &[String]) -> Vec { let mut commands = vec![]; let mut aliases: Vec = core.db.get_indexes_all("BASH_ALIASES").to_vec(); commands.append(&mut aliases); let mut functions: Vec = core.db.functions.keys().cloned().collect(); commands.append(&mut functions); let mut vars: Vec = core.db.get_param_keys(); commands.append(&mut vars); let head = get_head(args, 2); if !head.is_empty() { commands.retain(|a| a.starts_with(&head)); } commands } pub fn compgen_o(core: &mut ShellCore, args: &[String]) -> Vec { let mut commands = vec![]; let mut options: Vec = core.options.get_keys(); commands.append(&mut options); let head = get_head(args, 2); if !head.is_empty() { commands.retain(|a| a.starts_with(&head)); } commands } fn compgen_large_g(core: &mut ShellCore, args: &[String]) -> Vec { let glob = args[2].to_string(); path_expansion::expand(&glob, &core.shopts) } fn compgen_large_w(core: &mut ShellCore, args: &[String]) -> Vec { let mut ans: Vec = vec![]; let mut words = args[2].to_string(); if words.starts_with("$") { if let Ok(value) = core.db.get_param(&args[2][1..]) { words = value; } } let mut feeder = Feeder::new(&words); while !feeder.is_empty() { match Word::parse(&mut feeder, core, None) { Ok(Some(mut w)) => { if let Ok(mut v) = w.eval(core) { ans.append(&mut v); } } _ => { let len = feeder.scanner_multiline_blank(core); feeder.consume(len); } } } drop_unmatch(args, 3, &mut ans); ans } pub fn compgen_u(_: &mut ShellCore, args: &[String]) -> Vec { let mut ans = vec![]; if let Ok(f) = File::open("/etc/passwd") { for line in BufReader::new(f).lines() { match line { Ok(line) => { let splits: Vec<&str> = line.split(':').collect(); ans.push(splits[0].to_string()); } _ => return vec![], } } } drop_unmatch(args, 2, &mut ans); ans } pub fn compgen_shopt(core: &mut ShellCore, args: &[String]) -> Vec { let mut ans = core.shopts.get_keys(); drop_unmatch(args, 2, &mut ans); ans } pub fn compgen_function(core: &mut ShellCore, args: &[String]) -> Vec { let mut ans = core.db.functions.keys().cloned().collect(); drop_unmatch(args, 2, &mut ans); ans } pub fn compgen_hostname(_: &mut ShellCore, _: &[String]) -> Vec { //TODO: Implement! vec![] } pub fn compgen_stopped(core: &mut ShellCore, args: &[String]) -> Vec { let mut ans = vec![]; for job in &core.job_table { if job.display_status == "Stopped" { ans.push(job.text.split(' ').next().unwrap().to_string()); } } drop_unmatch(args, 2, &mut ans); ans } pub fn compgen_j(core: &mut ShellCore, args: &[String]) -> Vec { let mut ans = vec![]; for job in &core.job_table { ans.push(job.text.split(' ').next().unwrap().to_string()); } drop_unmatch(args, 2, &mut ans); ans } ================================================ FILE: src/core/builtins/complete.rs ================================================ //SPDX-FileCopyrightText: 2023 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::core::{CompletionEntry, HashMap}; use crate::utils::arg; use crate::{builtins, ShellCore}; fn action_to_reduce_symbol(arg: &str) -> String { match arg { "file" => "f", "directory" => "d", "command" => "c", "alias" => "a", "builtin" => "b", "export" => "e", "group" => "g", "keyword" => "k", "variable" => "v", "setopt" => "o", "job" => "j", "service" => "s", "user" => "u", _ => "", } .to_string() } fn opt_to_action(arg: &str) -> String { match arg { "-a" => "alias", "-b" => "builtin", "-c" => "command", "-d" => "directory", "-e" => "export", "-f" => "file", "-g" => "group", "-k" => "keyword", "-j" => "job", "-o" => "setopt", "-u" => "user", "-v" => "variable", _ => "", } .to_string() } fn print_complete(core: &mut ShellCore) -> i32 { if !core.completion.default_function.is_empty() { println!("complete -F {} -D", &core.completion.default_function); } for (name, info) in &core.completion.entries { if !info.large_w_cands.is_empty() { print!("complete -W {} ", &info.large_w_cands); } else if !info.function.is_empty() { print!("complete -F {} ", &info.function); } else if !info.action.is_empty() { let symbol = action_to_reduce_symbol(&info.action); if symbol.is_empty() { print!("complete -A {} ", &info.action); } else { print!("complete -{} ", &symbol); } if info.options.contains_key("-P") { print!("-P '{}' ", &info.options["-P"]); } if info.options.contains_key("-S") { print!("-S '{}' ", &info.options["-S"]); } } else { print!("complete "); } println!("{}", &name); } 0 } fn complete_f(core: &mut ShellCore, args: &[String], o_options: &[String]) -> i32 { let d_option = arg::has_option("-D", args); let mut arg_index = 1; if d_option { arg_index = 2; } if args.len() <= arg_index { return builtins::error_(2, &args[0], "-F: option requires an argument", core); } if d_option { core.completion.default_function = args[arg_index].clone(); 0 } else { let func = args[arg_index].clone(); for command in &args[arg_index + 1..] { if !core.completion.entries.contains_key(command) { core.completion .entries .insert(command.clone(), CompletionEntry::default()); } let info = &mut core.completion.entries.get_mut(command).unwrap(); info.function = func.clone(); info.o_options = o_options.to_owned(); } 0 } } fn complete_large_w(core: &mut ShellCore, args: &[String]) -> i32 { let mut args = args.to_vec(); let com = args.pop().unwrap(); core.completion .entries .insert(com.clone(), CompletionEntry::default()); let info = &mut core.completion.entries.get_mut(&com).unwrap(); info.large_w_cands = args[2].clone(); 0 } fn complete_r(core: &mut ShellCore, args: &[String]) -> i32 { for command in &args[1..] { core.completion.entries.remove(command); } 0 } pub fn complete(core: &mut ShellCore, args: &[String]) -> i32 { let args = args.to_owned(); if args.len() <= 1 || args[1] == "-p" { return print_complete(core); } let mut o_options = vec![]; let mut args = arg::dissolve_options(&args); if args[1] == "-W" { return complete_large_w(core, &args); } if arg::consume_arg("-r", &mut args) { return complete_r(core, &args); } while let Some(v) = arg::consume_with_next_arg("-o", &mut args) { o_options.push(v); } let mut options = HashMap::new(); let prefix = arg::consume_with_next_arg("-P", &mut args); if let Some(prefix) = prefix { options.insert("-P".to_string(), prefix.clone()); } let suffix = arg::consume_with_next_arg("-S", &mut args); if let Some(suffix) = suffix { options.insert("-S".to_string(), suffix.clone()); } let action = opt_to_action(&args[1]); if !action.is_empty() { for command in &args[2..] { if !core.completion.entries.contains_key(command) { core.completion .entries .insert(command.clone(), CompletionEntry::default()); } let info = &mut core.completion.entries.get_mut(command).unwrap(); info.action = action.clone(); info.options = options.clone(); } return 0; } if args.len() > 3 && args[1] == "-A" { for command in &args[3..] { if !core.completion.entries.contains_key(command) { core.completion .entries .insert(command.clone(), CompletionEntry::default()); } let info = &mut core.completion.entries.get_mut(command).unwrap(); info.action = args[2].clone(); info.options = options.clone(); } return 0; } if arg::consume_arg("-F", &mut args) { complete_f(core, &args, &o_options) } else { let msg = format!("{}: still unsupported", &args[1]); builtins::error_(1, &args[0], &msg, core) } } ================================================ FILE: src/core/builtins/compopt.rs ================================================ //SPDX-FileCopyrightText: 2023 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::core::CompletionEntry; use crate::utils::arg; use crate::ShellCore; fn compopt_set(info: &mut CompletionEntry, plus: &[String], minus: &[String]) -> i32 { for opt in minus { //add if !info.o_options.contains(opt) { info.o_options.push(opt.to_string()); } } for opt in plus { //remove info.o_options.retain(|e| e != opt); } 0 } fn compopt_print(core: &mut ShellCore, args: &[String]) -> i32 { let optlist = [ "bashdefault", "default", "dirnames", "filenames", "noquote", "nosort", "nospace", "plusdirs", ]; let optlist: Vec = optlist.iter().map(|s| s.to_string()).collect(); let com = args[1].clone(); if core.completion.entries.contains_key(&com) { let info = &core.completion.entries.get_mut(&com).unwrap(); print!("compopt "); for opt in &optlist { match info.o_options.contains(opt) { true => print!("-o {opt} "), false => print!("+o {opt} "), } } println!("{}", &com); } else { eprintln!("sush: compopt: {}: no completion specification", &args[1]); return 1; } 0 } pub fn compopt(core: &mut ShellCore, args: &[String]) -> i32 { let mut args = args.to_owned(); if args.len() < 2 { dbg!("{:?}", &core.completion.entries); return 1; } if !args[1].starts_with("-") && !args[1].starts_with("+") { return compopt_print(core, &args); } let mut flag = "".to_string(); let mut minus = vec![]; let mut plus = vec![]; let mut minus_d = vec![]; let mut plus_d = vec![]; let mut minus_e = vec![]; let mut plus_e = vec![]; while args.len() > 1 { if args[1] == "-D" || args[1] == "-E" { flag = args[1].clone(); args.remove(1); continue; } if args[1] == "-o" { let opt = arg::consume_with_next_arg("-o", &mut args); if opt.is_none() { return 1; } match flag.as_str() { "" => minus.push(opt.unwrap()), "-D" => minus_d.push(opt.unwrap()), "-E" => minus_e.push(opt.unwrap()), _ => return 1, } continue; } if args[1] == "+o" { let opt = arg::consume_with_next_arg("+o", &mut args); if opt.is_none() { return 1; } match flag.as_str() { "" => plus.push(opt.unwrap()), "-D" => plus_d.push(opt.unwrap()), "-E" => plus_e.push(opt.unwrap()), _ => return 1, } continue; } break; } let info = if args.len() == 1 { &mut core.completion.current } else if args.len() == 2 { match core.completion.entries.get_mut(&args[1]) { Some(i) => i, None => return 1, } } else { return 1; }; compopt_set(info, &plus, &minus) //TODO: support of -D -E } ================================================ FILE: src/core/builtins/echo.rs ================================================ //SPDX-FileCopyrightText: 2025 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::elements::ansi_c_str::AnsiCString; use crate::error::exec::ExecError; use crate::utils::c_string; use crate::{Feeder, ShellCore}; use std::ffi::CString; use std::io; use std::io::{stdout, Write}; fn arg_to_c_str(arg: &str, core: &mut ShellCore) -> Result { let mut f = Feeder::new(arg); let ans = match AnsiCString::parse(&mut f, core, true) { Ok(Some(mut ansi_c_str)) => c_string::to_carg(&ansi_c_str.eval()), Ok(None) => c_string::to_carg(arg), Err(e) => return Err(ExecError::ParseError(e)), }; Ok(ans) } pub fn echo(core: &mut ShellCore, args: &[String]) -> i32 { let mut args = args.to_owned(); let mut first = true; let mut e_opt = false; let mut n_opt = false; if args.len() == 1 { println!(); return 0; } match args[1].as_ref() { "-ne" | "-en" => { e_opt = true; n_opt = true; args.remove(1); } "-e" => { e_opt = true; args.remove(1); } "-n" => { n_opt = true; args.remove(1); } _ => {} } for a in &args[1..] { if !first { let _ = io::stdout().write(b" "); } first = false; let bytes = match e_opt { false => c_string::to_carg(a).into_bytes(), true => match arg_to_c_str(a, core) { Ok(v) => v.into_bytes(), Err(e) => { e.print(core); return 1; } }, }; io::stdout().write_all(&bytes).unwrap(); } if !n_opt { let _ = io::stdout().write(b"\n"); } stdout().flush().unwrap(); 0 } ================================================ FILE: src/core/builtins/exec.rs ================================================ //SPDX-FileCopyrightText: 2025 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::{proc_ctrl, ShellCore}; use nix::errno::Errno; use nix::unistd; use std::ffi::CString; pub fn exec(core: &mut ShellCore, args: &[String]) -> i32 { let args = args.to_owned(); if core.db.flags.contains('r') { return super::error_(1, &args[0], "restricted", core); } if args.len() == 1 { return 0; } if core.db.flags.contains('i') || core.shopts.query("execfail") { exec_command(&args[1..], core, "") } else { proc_ctrl::exec_command(&args[1..], core, "") } } fn exec_command(args: &[String], core: &mut ShellCore, fullpath: &str) -> i32 { let cargs: Vec = args .iter() .map(|a| CString::new(a.to_string()).unwrap()) .collect(); let cfullpath = CString::new(fullpath).unwrap(); if !fullpath.is_empty() { let _ = unistd::execv(&cfullpath, &cargs); } let result = unistd::execvp(&cargs[0], &cargs); match result { Err(Errno::E2BIG) => super::error_(126, &args[0], "Arg list too long", core), Err(Errno::EACCES) => { super::error_(126, &args[0], "cannot execute: Permission denied", core) } Err(Errno::ENOENT) => super::error_(127, &args[0], "No such file or directory", core), Err(e) => { let msg = format!("{:?}", &e); super::error_(127, &args[0], &msg, core) } _ => 127, } } ================================================ FILE: src/core/builtins/getopts.rs ================================================ //SPDX-FileCopyrightText: 2024 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::{arg, error, ShellCore}; #[derive(Debug)] enum Opt { Single(String), WithArg(String), } struct NoArgOpt<'a>{ name: &'a str, arg: &'a str, index: usize, subindex: usize, subarg: bool, exp_args_len: usize, silence: bool, scope: Option, } impl Opt { fn is_single(&self, opt: &str) -> bool { match self { Self::Single(s) => s == opt, _ => false, } } fn is_witharg(&self, opt: &str) -> bool { match self { Self::WithArg(s) => s == opt, _ => false, } } } fn parse(optstring: &str) -> (Vec, bool) { let mut optstring = optstring.to_string(); let mut ans = vec![]; let mut silence = false; if optstring.starts_with(":") { optstring.remove(0); silence = true; } for c in optstring.chars() { if c == ':' { match ans.pop() { Some(Opt::Single(opt)) => ans.push(Opt::WithArg(opt)), _ => return (vec![], silence), } } else { let opt = format!("-{c}"); ans.push(Opt::Single(opt)); } } (ans, silence) } pub fn get_index(core: &mut ShellCore) -> (usize, usize) { //index, subindex let mut index = 1; let mut subindex = 0; if let Ok(s) = core.db.get_param("OPTIND") { if let Ok(n) = s.parse::() { index = n; } if let Ok(p) = core.db.get_param("OPTIND_PREV") { if let Ok(prev) = p.parse::() { if index != prev { let _ = core.db.set_param("OPTIND_SUB", "0", None); } } } } if let Ok(s) = core.db.get_param("OPTIND_SUB") { if let Ok(n) = s.parse::() { subindex = n; } } (index, subindex) } fn set_no_arg_option(no_arg_opt: &NoArgOpt, core: &mut ShellCore,) -> i32 { let result = core.db.set_param(no_arg_opt.name, &no_arg_opt.arg[1..], no_arg_opt.scope); core.db.set_param("OPTARG", "", no_arg_opt.scope).ok(); if !no_arg_opt.subarg || no_arg_opt.subindex + 1 == no_arg_opt.exp_args_len { let _ = core.db.set_param("OPTIND", &(no_arg_opt.index + 1).to_string(), no_arg_opt.scope); let _ = core.db.set_param("OPTIND_SUB", "0", no_arg_opt.scope); let _ = core.db.set_param("OPTIND_PREV", &(no_arg_opt.index + 1).to_string(), no_arg_opt.scope); } else { let _ = core.db.set_param("OPTIND", &no_arg_opt.index.to_string(), no_arg_opt.scope); let _ = core.db.set_param("OPTIND_SUB", &(no_arg_opt.subindex + 1).to_string(), no_arg_opt.scope); let _ = core.db.set_param("OPTIND_PREV", &no_arg_opt.index.to_string(), no_arg_opt.scope); } if let Err(e) = result { if !no_arg_opt.silence { let msg = format!("getopts: {:?}", &e); error::print(&msg, core); } return 1; } 0 } fn set_option_with_arg( name: &str, arg: &str, index: usize, optarg: &str, silence: bool, core: &mut ShellCore, scope: Option, ) -> i32 { let result = core.db.set_param(name, &arg[1..], scope); let _ = core.db.set_param("OPTARG", optarg, scope); let _ = core.db.set_param("OPTIND", &(index + 2).to_string(), scope); let _ = core .db .set_param("OPTIND_PREV", &(index + 2).to_string(), scope); let _ = core.db.set_param("OPTIND_SUB", "0", scope); if let Err(e) = result { let _ = core .db .set_param("OPTIND_PREV", &(index + 10).to_string(), scope); if !silence { let msg = format!("getopts: {:?}", &e); error::print(&msg, core); } return 1; } 0 } pub fn getopts(core: &mut ShellCore, args: &[String]) -> i32 { let args = args.to_owned(); let scope = core.db.get_scope_pos("OPTIND").unwrap_or(0); let scope_sub = core.db.get_scope_pos("OPTIND_SUB").unwrap_or(0); let scope_prev = core.db.get_scope_pos("OPTIND_PREV").unwrap_or(0); if scope_sub != scope { let _ = core.db.set_param("OPTIND_SUB", "0", Some(scope)); } if scope_prev != scope { let _ = core.db.set_param("OPTIND_PREV", "0", Some(scope)); } let _ = core.db.set_param("OPTARG", "", Some(scope)); if args.len() < 3 { error::print("getopts: usage: getopts optstring name [arg ...]", core); return 2; } let (targets, silence) = parse(&args[1]); if targets.is_empty() { return 1; } let (index, subindex) = get_index(core); let name = args[2].clone(); let _ = core.db.set_param(&name, "?", Some(scope)); let args = match args.len() > 3 { true => &args[2..], false => &core.db.position_parameters.last().unwrap().clone(), }; if args.len() <= index { return 1; } let mut arg = args[index].clone(); let exp_args = arg::dissolve_options(&[arg.clone()]); let subarg = exp_args.len() > 1; if subarg { if exp_args.len() <= subindex { return 1; } arg = exp_args[subindex].clone(); } if !arg.starts_with("-") { return 1; } if arg.starts_with("--") { let _ = core .db .set_param("OPTIND", &(index + 1).to_string(), Some(scope)); let _ = core .db .set_param("OPTIND_PREV", &(index + 1).to_string(), Some(scope)); return 1; } if targets.iter().any(|t| t.is_single(&arg)) { let no_arg_opt = NoArgOpt { name: &name, arg: &arg, index, subindex, subarg, exp_args_len: exp_args.len(), silence, scope: Some(scope), }; return set_no_arg_option(&no_arg_opt, core); } if targets.iter().any(|t| t.is_witharg(&arg)) { let optarg = match args.len() > index + 1 { true => args[index + 1].clone(), false => return 1, }; return set_option_with_arg(&name, &arg, index, &optarg, silence, core, Some(scope)); } 1 } ================================================ FILE: src/core/builtins/hash.rs ================================================ //SPDX-FileCopyrightText: 2025 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::utils::arg; use crate::ShellCore; fn print_all(core: &mut ShellCore) -> i32 { println!("hits command"); for com in core.db.get_indexes_all("BASH_CMDS") { if let Ok(path) = core.db.get_elem("BASH_CMDS", &com) { if let Some(n) = core.db.hash_counter.get(&com) { println!("{:4}\t{}", &n, &path); } else { core.db.hash_counter.insert(com, 0); println!(" 0\t{}", &path); } } } 0 } pub fn hash(core: &mut ShellCore, args: &[String]) -> i32 { let args = args.to_owned(); let mut args = arg::dissolve_options(&args); if args.len() == 1 { return print_all(core); } if arg::consume_arg("-p", &mut args) { if args.len() == 1 { return super::error_(1, "hash", "-p: option requires an argument", core); } if args.len() == 2 { return super::error_(1, "hash", "still not implemented", core); } if let Err(e) = core .db .set_assoc_elem("BASH_CMDS", &args[2], &args[1], Some(0)) { let msg = String::from(&e); return super::error_(1, "hash", &msg, core); } } 0 } ================================================ FILE: src/core/builtins/history.rs ================================================ //SPDX-FileCopyrightText: 2024 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::utils::arg; use crate::ShellCore; use std::fs::File; use std::io::{BufRead, BufReader}; pub fn history_c(core: &mut ShellCore) -> i32 { core.rewritten_history.clear(); core.history.clear(); 0 } pub fn history(core: &mut ShellCore, args: &[String]) -> i32 { let args = args.to_owned(); let mut args = arg::dissolve_options(&args); if arg::consume_arg("-c", &mut args) { return history_c(core); } if args.len() > 1 { let msg = format!("{}: invalid option", &args[1]); return super::error_(1, "history", &msg, core); } let mut number = 1; let filename = core.db.get_param("HISTFILE").unwrap_or_default(); if filename.is_empty() { return 0; } let file = match File::open(&filename) { Ok(f) => f, _ => return 0, }; let f = BufReader::new(file); for line in f.lines() { println!("{:5} {}", number, &line.unwrap()); number += 1; } for h in core.history.iter().rev() { println!("{:5} {}", number, &h); number += 1; } 0 } ================================================ FILE: src/core/builtins/job_commands.rs ================================================ //SPDX-FileCopyrightText: 2024 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use libc; use crate::core::JobEntry; use crate::utils::arg; use crate::ShellCore; use crate::{signal, utils}; use nix::sys::signal::Signal; use nix::unistd; use nix::unistd::Pid; use std::{thread, time}; //use std::sync::atomic::Ordering::Relaxed; fn pid_to_array_pos(pid: i32, jobs: &[JobEntry]) -> Option { (0..jobs.len()).find(|&i| jobs[i].pids[0].as_raw() == pid) } fn jobid_to_pos(id: usize, jobs: &mut [JobEntry]) -> Option { for (i, job) in jobs.iter_mut().enumerate() { if job.id == id { return Some(i); } } None } pub fn bg(core: &mut ShellCore, args: &[String]) -> i32 { let args = args.to_owned(); if core.job_table.is_empty() { return 1; } let mut args = arg::dissolve_options(&args); if !core.db.flags.contains('m') { return super::error_(1, &args[0], "no job control", core); } if arg::consume_arg("-s", &mut args) { return super::error_(1, &args[0], "-s: invalid option", core); } let pos = match args.len() { 1 => { let id = core.job_table_priority[0]; jobid_to_pos(id, &mut core.job_table) } 2 => jobspec_to_array_pos(core, &args[0], &args[1]), _ => None, }; match pos { Some(p) => { let id = core.job_table[p].id; if core.job_table[p].no_control { let msg = format!("job {} started without job control", &id); return super::error_(1, &args[0], &msg, core); } if core.job_table[p].display_status == "Running" { let msg = format!("job {} already in background", &id); return super::error_(0, &args[0], &msg, core); } let priority = get_priority(core, p); let symbol = match priority { 0 => "+", 1 => "-", _ => " ", }; println!("[{}]{} {} &", &id, &symbol, &core.job_table[p].text); core.job_table[p].send_cont() } None => return 1, } 0 } pub fn fg(core: &mut ShellCore, args: &[String]) -> i32 { let args = args.to_owned(); let mut args = arg::dissolve_options(&args); if !core.db.flags.contains('m') { return super::error_(1, &args[0], "no job control", core); } if arg::consume_arg("-s", &mut args) { return super::error_(1, &args[0], "-s: invalid option", core); } let id = if args.len() == 1 { if core.job_table_priority.is_empty() { return 1; } core.job_table_priority[0] } else if args.len() == 2 { match jobspec_to_array_pos(core, &args[0], &args[1]) { Some(pos) => core.job_table[pos].id, None => return 1, } } else { return 1; }; let pos = match jobid_to_pos(id, &mut core.job_table) { Some(i) => i, _ => return 1, }; if core.job_table[pos].no_control { let id = core.job_table[pos].id; let msg = format!("job {} started without job control", &id); return super::error_(1, &args[0], &msg, core); } let pgid = core.job_table[pos].solve_pgid(); if pgid.as_raw() == 0 { return 1; } signal::ignore(Signal::SIGTTOU); let mut exit_status = 1; if let Some(fd) = core.tty_fd.as_ref() { //if unistd::tcsetpgrp(fd, pgid).is_ok() { if core.fds.tcsetpgrp(*fd, pgid).is_ok() { println!("{}", &core.job_table[pos].text); core.job_table[pos].send_cont(); exit_status = core.job_table[pos].update_status(true, false).unwrap_or(1); if let Ok(mypgid) = unistd::getpgid(Some(Pid::from_raw(0))) { let _ = core.fds.tcsetpgrp(*fd, mypgid); } } } else { println!("{}", &core.job_table[pos].text); core.job_table[pos].send_cont(); exit_status = core.job_table[pos].update_status(true, false).unwrap_or(1); } remove(core, pos); signal::restore(Signal::SIGTTOU); exit_status } fn jobspec_to_array_pos(core: &mut ShellCore, com: &str, jobspec: &str) -> Option { let poss = jobspec_to_array_poss(core, jobspec); if poss.is_empty() { let msg = format!("{}: no such job", &jobspec); super::error_(127, com, &msg, core); return None; } else if poss.len() > 1 { let msg = format!( "{}: ambiguous job spec", jobspec.strip_prefix('%').unwrap_or(jobspec) ); super::error_(127, com, &msg, core); return None; } Some(poss[0]) } fn jobspec_to_array_poss(core: &mut ShellCore, jobspec: &str) -> Vec { if jobspec.is_empty() { return (0..core.job_table.len()).collect(); } if core.job_table.is_empty() { return vec![]; } let s = &jobspec[1..]; let mut ans = vec![]; if let Ok(n) = s.parse::() { for (i, job) in core.job_table.iter_mut().enumerate() { if n == job.id { ans.push(i); } } } else if s == "%" || s == "+" { for (i, job) in core.job_table.iter_mut().enumerate() { if job.id == core.job_table_priority[0] { ans.push(i); } } } else if s == "-" { for (i, job) in core.job_table.iter_mut().enumerate() { if core.job_table_priority.len() < 2 { if job.id == core.job_table_priority[0] { ans.push(i); } } else if job.id == core.job_table_priority[1] { ans.push(i); } } } else if let Some(stripped) = s.strip_prefix('?') { for (i, job) in core.job_table.iter_mut().enumerate() { if job.text.contains(stripped) { ans.push(i); } } } else { for (i, job) in core.job_table.iter_mut().enumerate() { if job.text.starts_with(s) { ans.push(i); } } } ans } pub fn jobs(core: &mut ShellCore, args: &[String]) -> i32 { let mut args = arg::dissolve_options(args); if arg::consume_arg("-n", &mut args) { core.jobtable_print_status_change(); return 0; } let jobspecs = arg::consume_starts_with("%", &mut args); let jobspec = match jobspecs.last() { Some(s) => s.clone(), None => String::new(), }; if core.job_table.is_empty() && jobspec.is_empty() { return 0; } let poss = jobspec_to_array_poss(core, &jobspec); if poss.is_empty() { let msg = format!("{}: no such job", &jobspec); return super::error_(127, "jobs", &msg, core); } if poss.len() > 1 && !jobspec.is_empty() { let msg = format!( "{}: ambiguous job spec", jobspec.strip_prefix('%').unwrap_or(&jobspec) ); super::error_(127, "jobs", &msg, core); let msg = format!("{}: no such job", &jobspec); return super::error_(127, "jobs", &msg, core); } if arg::consume_arg("-p", &mut args) { for id in poss { core.job_table[id].print_p(); } return 0; } if !jobspec.is_empty() { let l_opt = arg::consume_arg("-l", &mut args); let r_opt = arg::consume_arg("-r", &mut args); let s_opt = arg::consume_arg("-s", &mut args); if core.job_table[poss[0]].print(&core.job_table_priority, l_opt, r_opt, s_opt, true) { remove(core, poss[0]); } return 0; } print(core, &args); 0 } fn get_priority(core: &mut ShellCore, pos: usize) -> usize { let id = core.job_table[pos].id; for i in 0..core.job_table_priority.len() { if core.job_table_priority[i] == id { return i; } } core.job_table.len() } fn print(core: &mut ShellCore, args: &[String]) { let l_opt = arg::has_option("-l", args); let r_opt = arg::has_option("-r", args); let s_opt = arg::has_option("-s", args); let mut rem = vec![]; for id in 0..core.job_table.len() { if core.job_table[id].print(&core.job_table_priority, l_opt, r_opt, s_opt, true) { rem.push(id); } } for pos in rem.into_iter().rev() { remove(core, pos); } } fn remove_coproc(core: &mut ShellCore, pos: usize) { if let Some(name) = &core.job_table[pos].coproc_name { let _ = core.db.unset(&name, None, false); let _ = core.db.unset(&(name.to_owned() + "_PID"), None, false); if let Ok(fd0) = core.db.get_elem(&name, "0") { if let Ok(n) = fd0.parse::() { let _ = unsafe{libc::close(n)}; } } if let Ok(fd1) = core.db.get_elem(&name, "1") { if let Ok(n) = fd1.parse::() { let _ = unsafe{libc::close(n)}; } } let _ = core.db.unset(&(name), None, false); } } fn remove(core: &mut ShellCore, pos: usize) { let job_id = core.job_table[pos].id; remove_coproc(core, pos); core.job_table.remove(pos); core.job_table_priority.retain(|id| *id != job_id); } fn wait_jobspec( core: &mut ShellCore, com: &str, jobspec: &str, var_name: &Option, f_opt: bool, ) -> (i32, bool) { match jobspec_to_array_pos(core, com, jobspec) { Some(pos) => wait_a_job(core, pos, var_name, f_opt), None => (127, false), } } fn wait_next( core: &mut ShellCore, ids: &[usize], var_name: &Option, f_opt: bool, ) -> (i32, bool) { if core.job_table_priority.is_empty() { return (127, false); } let mut exit_status = 0; let mut drop = 0; let mut end = false; let mut pid = String::new(); let mut remove_job = false; loop { /* dbg!("H"); if core.sigint.load(Relaxed) { dbg!("!!!"); }*/ thread::sleep(time::Duration::from_millis(10)); //0.1秒周期に変更 for (i, job) in core.job_table.iter_mut().enumerate() { if !ids.contains(&i) && !ids.is_empty() { continue; } if let Ok(es) = job.update_status(false, true) { if job.display_status == "Done" || job.display_status == "Killed" || (job.display_status == "Stopped" && !f_opt) { exit_status = es; drop = i; end = true; remove_job = job.display_status == "Done" || job.display_status == "Killed"; pid = job.pids[0].to_string(); break; } } } if end { break; } } if let Some(var) = var_name { let _ = core.db.unset(var, None, false); if let Err(e) = core.db.set_param(var, &pid, None) { e.print(core); } } if remove_job { remove(core, drop); } (exit_status, true) } fn wait_pid(core: &mut ShellCore, pid: i32, var_name: &Option, f_opt: bool) -> (i32, bool) { match pid_to_array_pos(pid, &core.job_table) { Some(i) => wait_a_job(core, i, var_name, f_opt), None => (127, false), } } fn wait_a_job( core: &mut ShellCore, pos: usize, var_name: &Option, f_opt: bool, ) -> (i32, bool) { if core.job_table.len() < pos { return ( super::error_(127, "wait", "invalpos jobpos", core), false, ); } let pid = core.job_table[pos].pids[0].to_string(); let ans = match core.job_table[pos].update_status(true, false) { Ok(n) => { if let Some(var) = var_name { let _ = core.db.unset(var, None, false); if let Err(e) = core.db.set_param(var, &pid, None) { e.print(core); } } (n, true) } Err(e) => { e.print(core); return (1, false); } }; if f_opt && core.job_table[pos].display_status == "Stopped" { wait_a_job(core, pos, var_name, f_opt) } else { remove(core, pos); ans } } fn wait_arg_job( core: &mut ShellCore, com: &str, arg: &str, var_name: &Option, f_opt: bool, ) -> (i32, bool) { if arg.starts_with("%") { return wait_jobspec(core, com, arg, var_name, f_opt); } if let Ok(pid) = arg.parse::() { return wait_pid(core, pid, var_name, f_opt); } (127, false) } fn wait_all(core: &mut ShellCore) -> i32 { let mut exit_status = 0; let mut remove_list = vec![]; for pos in 0..core.job_table.len() { match core.job_table[pos].update_status(true, false) { Ok(n) => { if core.job_table[pos].display_status == "Done" || core.job_table[pos].display_status == "Killed" { remove_list.push(pos); } exit_status = n; } Err(e) => { e.print(core); exit_status = 1; break; } } } for pos in remove_list.into_iter().rev() { remove(core, pos); } exit_status } fn wait_n( core: &mut ShellCore, args: &mut Vec, var_name: &Option, f_opt: bool, ) -> i32 { let mut jobs = arg::consume_with_subsequents("-n", args); jobs.remove(0); if jobs.is_empty() { return wait_next(core, &[], var_name, f_opt).0; } let mut ids = vec![]; for j in &jobs { if j.starts_with("%") { ids.append(&mut jobspec_to_array_poss(core, j)); } else { for (i, job) in core.job_table.iter_mut().enumerate() { if job.pids[0].to_string() == *j { ids.push(i); } } } } ids.sort(); ids.dedup(); let mut ans = -1; for _ in 0..ids.len() { let tmp = match ans { -1 => wait_next(core, &ids, var_name, f_opt), _ => wait_next(core, &ids, &None, f_opt), }; if tmp.1 && ans == -1 { ans = tmp.0; } } ans } pub fn wait(core: &mut ShellCore, args: &[String]) -> i32 { let args = args.to_owned(); if core.is_subshell { super::error_(127, &args[0], "called from subshell", core); } if args.len() <= 1 { return wait_all(core); } let mut args = arg::dissolve_options(&args); let var_name = arg::consume_with_next_arg("-p", &mut args); let f_opt = arg::consume_arg("-f", &mut args); if args.len() > 1 && args[1] == "-n" { return wait_n(core, &mut args, &var_name, f_opt); } if args.len() > 1 { return wait_arg_job(core, &args[0], &args[1], &var_name, f_opt).0; } 1 } /* TODO: implement original kill */ pub fn kill(core: &mut ShellCore, args: &[String]) -> i32 { let mut args = args.to_owned(); //let mut args = arg::dissolve_options(args); let path = utils::get_command_path(&args[0], core); match path.is_empty() { true => return 1, false => args[0] = path, } if args.len() >= 3 && args[2].starts_with("%") { match jobspec_to_array_pos(core, &args[0], &args[2]) { Some(id) => args[2] = core.job_table[id].pids[0].to_string(), None => return 1, } } let com = args[0].to_string(); for arg in args.iter_mut() { if arg == "-n" { *arg = "-s".to_string(); } if arg.starts_with("%") { if let Some(pos) = jobspec_to_array_pos(core, &com, arg) { *arg = core.job_table[pos].pids[0].to_string(); } else { let msg = format!("{}: no such job", &arg); return super::error_(127, "jobs", &msg, core); } } } /* args.insert(0, "eval".to_string()); super::eval(core, &args) */ super::run_external(core, &args, |es| es > 0) } pub fn disown(core: &mut ShellCore, args: &[String]) -> i32 { let mut args = arg::dissolve_options(args); let h_opt = arg::consume_arg("-h", &mut args); let _r_opt = arg::consume_arg("-r", &mut args); //TODO: implement if args.len() == 1 { let ids = jobspec_to_array_poss(core, "%%"); if ids.len() == 1 { remove_coproc(core, ids[0]); core.job_table.remove(ids[0]); core.job_table_priority.remove(0); return 0; } return 1; } if args.len() == 2 && args[1] == "-a" { core.job_table.clear(); core.job_table_priority.clear(); return 0; } for a in &args[1..] { if a.starts_with("-") { let msg = format!("{}: invalid option", &a); super::error_(127, &args[0], &msg, core); eprintln!("disown: usage: disown [-h] [-ar] [jobspec ... | pid ...]"); return 127; } } for a in &args[1..] { if let Some(pos) = jobspec_to_array_pos(core, &args[0], a) { if h_opt { //TODO: to make each job doesn't stop by SIGHUP } else { remove(core, pos); } } } 0 } ================================================ FILE: src/core/builtins/loop_control.rs ================================================ //SPDX-FileCopyrightText: 2024 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::ShellCore; pub fn return_(core: &mut ShellCore, args: &[String]) -> i32 { let args = args.to_owned(); if core.source_function_level <= 0 { eprintln!("sush: return: can only `return' from a function or sourced script"); return 2; } core.return_flag = true; if args.len() < 2 { return 0; } else if let Ok(n) = args[1].parse::() { return n % 256; } eprintln!("sush: return: {}: numeric argument required", args[1]); 2 } pub fn break_(core: &mut ShellCore, args: &[String]) -> i32 { let args = args.to_owned(); if core.loop_level <= 0 { eprintln!("sush: break: only meaningful in a `for', `while', or `until' loop"); return 0; } core.break_counter += 1; if args.len() < 2 { return 0; } match args[1].parse::() { Ok(n) => { if n > 0 { core.break_counter += n - 1; } else { eprintln!("sush: break: {}: loop count out of range", args[1]); return 1; } } Err(_) => { eprintln!("sush: break: {}: numeric argument required", args[1]); return 128; } }; 0 } pub fn continue_(core: &mut ShellCore, args: &[String]) -> i32 { let args = args.to_owned(); if core.loop_level <= 0 { eprintln!("sush: continue: only meaningful in a `for', `while', or `until' loop"); return 0; } //core.continue_counter += 1; core.continue_counter = 1; if args.len() < 2 { return 0; } match args[1].parse::() { Ok(n) => { if n > 0 { //core.continue_counter += n - 1; core.continue_counter = n; } else { eprintln!("sush: continue: {}: loop count out of range", args[1]); return 1; } } Err(_) => { eprintln!("sush: continue: {}: numeric argument required", args[1]); return 128; } }; 0 } ================================================ FILE: src/core/builtins/option.rs ================================================ //SPDX-FileCopyrightText: 2024 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::error::exec::ExecError; use crate::utils::arg; use crate::{error, ShellCore}; pub fn set_positions(core: &mut ShellCore, args: &[String]) -> Result<(), ExecError> { let com = match core.db.position_parameters.pop() { Some(scope) => { if scope.is_empty() { "".to_string() } else { scope[0].clone() } } None => return Err(ExecError::Other("empty param stack".to_string())), }; let mut tmp = args.to_vec(); if !tmp.is_empty() { tmp[0] = com; } else { tmp.push(com); } core.db.position_parameters.push(tmp); Ok(()) } pub fn set_positions_c(core: &mut ShellCore, args: &[String]) -> Result<(), ExecError> { if core.db.position_parameters.pop().is_none() { return Err(ExecError::Other("empty param stack".to_string())); } core.db.position_parameters.push(args.to_vec()); Ok(()) } fn check_invalid_options(args: &[String]) -> Result<(), ExecError> { for a in args { if a.starts_with("-") { return Err(ExecError::InvalidOption( "set: ".to_owned() + &a.to_string(), )); } } Ok(()) } pub fn set_options(core: &mut ShellCore, args: &mut Vec) -> Result<(), ExecError> { set_short_options(core, args); check_invalid_options(args) } pub fn set_short_options(core: &mut ShellCore, args: &mut Vec) { for (short, long) in [ ('a', "allexport"), ('t', "onecmd"), ('m', "monitor"), ('C', "noclobber"), ('a', "allexport"), ('B', "braceexpand"), ('f', ""), ('u', ""), ('e', ""), ('r', ""), ('H', ""), ('x', ""), ('v', ""), ] { let minus_opt = format!("-{short}"); let plus_opt = format!("+{short}"); if arg::consume_option(&minus_opt, args) { if !core.db.flags.contains(short) { core.db.flags += &minus_opt[1..]; } if !long.is_empty() { let _ = core.options.set(long, true); } } if arg::consume_option(&plus_opt, args) { core.db.flags.retain(|f| f != short); if !long.is_empty() { let _ = core.options.set(long, false); } } } } fn set_bash_flags(core: &mut ShellCore, args: &[String]) { let positive = args[1] == "-o"; if args[2] == "monitor" { if positive && !core.db.flags.contains('m') { core.db.flags.push('m'); } else if !positive { core.db.flags.retain(|f| f != 'm'); } }else if args[2] == "allexport" { if positive && !core.db.flags.contains('a') { core.db.flags.push('a'); } else if !positive { core.db.flags.retain(|f| f != 'a'); } } } pub fn set(core: &mut ShellCore, args: &[String]) -> i32 { let mut args = arg::dissolve_options(args); if core.db.flags.contains('r') && arg::consume_arg("+r", &mut args) { let _ = super::error_(1, &args[0], "+r: invalid option", core); eprintln!("set: usage: set [-abefhkmnptuvxBCEHPT] [-o option-name] [--] [-] [arg ...]"); return 1; } if args.len() <= 1 { core.db.print_params_and_funcs(); return 0; } set_short_options(core, &mut args); if args.len() < 2 { return 0; } if args[1] == "--" || args[1] == "-" { args[1] = core.db.position_parameters[0][0].clone(); args.remove(0); match set_positions(core, &args) { Ok(()) => return 0, Err(e) => { return super::error_(1, &args[0], &String::from(&e), core); } } } if args[1] == "-o" || args[1] == "+o" { let positive = args[1] == "-o"; if args.len() == 2 { core.options.print_all(positive); return 0; } else { set_bash_flags(core, &args); /* if args[2] == "monitor" { if positive && !core.db.flags.contains('m') { core.db.flags.push('m'); } else if !positive { core.db.flags.retain(|f| f != 'm'); } }else if args[2] == "allexport" { if positive && !core.db.flags.contains('a') { core.db.flags.push('a'); } else if !positive { core.db.flags.retain(|f| f != 'a'); } }*/ return match core.options.set(&args[2], positive) { Ok(()) => 0, Err(e) => { return super::error_(2, &args[0], &String::from(&e), core); } }; } } if !args[1].starts_with("-") && !args[1].starts_with("+") { if let Err(e) = set_positions(core, &args) { e.print(core); return 2; } else { return 0; } } if let Err(e) = check_invalid_options(&args) { e.print(core); return 2; } 0 } pub fn shift(core: &mut ShellCore, args: &[String]) -> i32 { let args = args.to_owned(); if args.len() == 1 { let mut last = core.db.position_parameters.pop().unwrap(); if last.len() > 1 { last.remove(1); } core.db.position_parameters.push(last); return 0; } if args.len() == 2 { let n = match args[1].parse::() { Ok(n) => n, Err(_) => { let err = format!("shift: {}: numeric argument required", &args[1]); error::print(&err, core); return 1; } }; if n < 0 { let err = format!("shift: {}: shift count out of range", &args[1]); error::print(&err, core); return 1; } let mut last = core.db.position_parameters.pop().unwrap(); for _ in 0..n { if last.len() == 1 { break; } last.remove(1); } core.db.position_parameters.push(last); return 0; } error::print("shift: too many arguments", core); 1 } pub fn shopt_print(core: &mut ShellCore, args: &[String], all: bool) -> i32 { if all { core.shopts.print_all(true); return 0; } let mut res = true; match args[1].as_str() { "-s" => core.shopts.print_if(true), "-u" => core.shopts.print_if(false), "-q" => return 0, opt => res = core.shopts.print_opt(opt, false), } match res { true => 0, false => 1, } } pub fn shopt(core: &mut ShellCore, args: &[String]) -> i32 { let mut args = arg::dissolve_options(args); let print = arg::consume_arg("-p", &mut args); let o_opt = arg::consume_arg("-o", &mut args); let q_opt = arg::consume_arg("-q", &mut args); /* print section */ if print && o_opt { if args.len() >= 2 && !q_opt { core.options.print_opt(&args[1], true); } else if !q_opt { core.options.print_all(false); } return 0; } /* q option */ if q_opt { for a in &args[1..] { if ! core.shopts.query(a) { return 1; } } } if args.len() < 3 { // "shopt" or "shopt option" if !q_opt { let len = args.len(); return shopt_print(core, &args, len < 2); } return 0; } /* end of print section */ if o_opt { let opt = match args[1].as_str() { "-s" => "-o", "-u" => "+o", other => other, } .to_string(); let mut args_for_set = vec!["set".to_string(), opt]; args_for_set.append(&mut args[2..].to_vec()); return set(core, &args_for_set); } match args[1].as_str() { //TODO: args[3..] must to be set "-s" => { if core.shopts.implemented.contains(&args[2]) { match core.shopts.set(&args[2], true) { Ok(()) => 0, Err(e) => { e.print(core); 1 } } } else { let msg = format!("shopt: {}: not supported yet", &args[2]); error::print(&msg, core); 1 } } "-q" => { for arg in &args[2..] { if !core.shopts.exist(arg) { let msg = format!("shopt: {}: invalid shell option name", &arg); error::print(&msg, core); return 1; } if !core.shopts.query(arg) { return 1; } } 0 } "-u" => match core.shopts.set(&args[2], false) { Ok(()) => 0, Err(e) => { e.print(core); 1 } }, arg => { eprintln!("sush: shopt: {arg}: invalid shell option name"); eprintln!("shopt: usage: shopt [-su] [optname ...]"); 1 } } } ================================================ FILE: src/core/builtins/printf.rs ================================================ //SPDX-FileCopyrightText: 2024 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::elements::substitution::Substitution; use crate::error::arith::ArithError; use crate::error::exec::ExecError; use crate::{error, Feeder, ShellCore}; use std::io::{stdout, Write}; #[derive(Debug, Clone)] enum PrintfToken { B(String), DI(String), F(String), O(String, bool), S(String), U(String), X(String, bool), LargeX(String, bool), Q, Other(String), Normal(String), EscapedChar(char), } impl PrintfToken { fn continue_(&self) -> bool { !matches!(self, Self::Normal(_) | Self::EscapedChar(_)) } fn to_int(s: &String) -> Result { if s.starts_with("'") && s.len() > 1 { let ch = match s.chars().nth(1) { Some(n) => n, None => return Err(ExecError::Other("Invalid char: ".to_owned() + s)), }; return Ok(ch as isize); } match s.parse::() { Ok(n) => Ok(n), Err(_) => Err(ArithError::InvalidNumber(s.to_string()).into()), } } fn to_float(s: &String) -> Result { match s.parse::() { Ok(n) => Ok(n), Err(_) => Err(ArithError::InvalidNumber(s.to_string()).into()), } } //TODO: implement! fn padding_float(_: &mut String, mut _fmt: String) { if _fmt.is_empty() {} } fn padding(s: &mut String, mut fmt: String, is_int: bool) { if fmt.is_empty() { return; } let mut right = false; let mut padding = ' '; if fmt.starts_with("-") { fmt.remove(0); right = true; } if fmt.starts_with("0") { padding = fmt.remove(0); } if !is_int { padding = ' '; } let len = fmt.parse::().unwrap_or(0); if right { while s.len() < len { s.push(' '); } return; } if is_int && s.starts_with("-") && padding == '0' { while s.len() < len { s.insert(1, '0'); } return; } while s.len() < len { s.insert(0, padding); } } fn render_value(&mut self, args: &mut Vec) -> Result { match self { Self::DI(fmt) => { let mut a = pop(args); Self::padding(&mut a, fmt.clone(), true); Ok(a) } Self::U(fmt) => { let mut a = pop(args); if a.starts_with("-") { a.remove(0); let mut num = a.parse::()?; num = u64::MAX - num + 1; let mut a = num.to_string(); Self::padding(&mut a, fmt.clone(), true); Ok(a) } else { Self::padding(&mut a, fmt.clone(), true); Ok(a) } } Self::F(fmt) => { let mut a = format!("{:.6}", Self::to_float(&pop(args))?); Self::padding_float(&mut a, fmt.clone()); Ok(a) } Self::S(fmt) => { let mut a = pop(args); Self::padding(&mut a, fmt.clone(), false); Ok(a) } Self::B(fmt) => { let mut a = replace_escape(&pop(args)); Self::padding(&mut a, fmt.clone(), false); Ok(a) } Self::X(fmt, hash) => { let mut a = format!("{:x}", Self::to_int(&pop(args))?); if *hash { a = "0x".to_owned() + &a; } Self::padding(&mut a, fmt.clone(), true); Ok(a) } Self::O(fmt, hash) => { let mut a = format!("{:o}", Self::to_int(&pop(args))?); if *hash { a.insert(0, '0'); } Self::padding(&mut a, fmt.clone(), true); Ok(a) } Self::LargeX(fmt, hash) => { let mut a = format!("{:X}", Self::to_int(&pop(args))?); if *hash { a = "0X".to_owned() + &a; } Self::padding(&mut a, fmt.clone(), true); Ok(a) } Self::Q => { let a = pop(args); let mut q = a .replace("\\", "\\\\") .replace("$", "\\$") .replace("|", "\\|") .replace("\"", "\\\"") .replace("'", "\\\'") .replace("~", "\\~") .replace("(", "\\(") .replace(")", "\\)") .replace("{", "\\{") .replace("}", "\\}") .replace("!", "\\!") .replace("&", "\\&"); if q == "" { q = "''".to_string(); } Ok(q) } Self::Other(s) => { let a = pop(args); let formatted = match sprintf::sprintf!(&s, a) { Ok(res) => res, Err(e) => { let msg = format!("{} {} {}", &e, &s, &a); return Err(ExecError::Other(msg)); } }; Ok(formatted) } Self::EscapedChar(c) => Ok(esc_to_str(*c)), Self::Normal(s) => Ok(s.clone()), } } } fn pop(args: &mut Vec) -> String { match args.is_empty() { true => "".to_string(), false => args.remove(0), } } fn esc_to_str(ch: char) -> String { match ch { 'a' => char::from(7).to_string(), 'b' => char::from(8).to_string(), 'e' | 'E' => char::from(27).to_string(), 'f' => char::from(12).to_string(), 'n' => "\n".to_string(), 'r' => "\r".to_string(), 't' => "\t".to_string(), 'v' => char::from(11).to_string(), '\\' => "\\".to_string(), '\'' => "'".to_string(), '"' => "\"".to_string(), _ => ("\\".to_owned() + &ch.to_string()).to_string(), } } fn replace_escape(s: &str) -> String { let mut ans = String::new(); let mut esc = false; for ch in s.chars() { if esc || ch == '\\' { if esc { ans.push_str(&esc_to_str(ch)); } esc = !esc; continue; } ans.push(ch); } ans } fn scanner_normal(remaining: &str) -> usize { let mut pos = 0; for c in remaining.chars() { if c == '%' || c == '\\' { break; } pos += c.len_utf8(); } pos } fn scanner_escaped_char(remaining: &str) -> usize { if !remaining.starts_with("\\") { return 0; } match remaining.chars().nth(1) { Some(ch) => 1 + ch.len_utf8(), _ => 0, } } fn scanner_hash(remaining: &str) -> usize { let mut ans = 0; for c in remaining.chars() { if c != '#' { break; } ans += 1; } ans } fn scanner_format_num(remaining: &str) -> usize { let mut ans = 0; for c in remaining.chars() { if !"-.".contains(c) && !c.is_ascii_digit() { break; } ans += 1; } ans } fn parse(pattern: &str) -> Vec { let mut remaining = pattern.to_string(); let mut ans = vec![]; while !remaining.is_empty() { let len = scanner_normal(&remaining); if len > 0 { let tail = remaining.split_off(len); ans.push(PrintfToken::Normal(remaining)); remaining = tail; continue; } let len = scanner_escaped_char(&remaining); if len > 0 { remaining.remove(0); ans.push(PrintfToken::EscapedChar(remaining.remove(0))); continue; } if remaining.starts_with("%") { remaining.remove(0); // % let mut has_hash = false; let mut num_part = String::new(); let len = scanner_hash(&remaining); if len > 0 { let tail = remaining.split_off(len); has_hash = true; remaining = tail; } let len = scanner_format_num(&remaining); if len > 0 { let tail = remaining.split_off(len); num_part = remaining.clone(); remaining = tail; } let token = match remaining.chars().next() { Some('b') => PrintfToken::B(num_part), Some('d') => PrintfToken::DI(num_part), Some('i') => PrintfToken::DI(num_part), Some('f') => PrintfToken::F(num_part), Some('o') => PrintfToken::O(num_part, has_hash), Some('s') => PrintfToken::S(num_part), Some('u') => PrintfToken::U(num_part), Some('x') => PrintfToken::X(num_part, has_hash), Some('X') => PrintfToken::LargeX(num_part, has_hash), Some('q') => PrintfToken::Q, Some(c) => PrintfToken::Other("%".to_owned() + &num_part + &c.to_string()), None => PrintfToken::Normal("%".to_string()), }; remaining.remove(0); ans.push(token); } } ans } fn format(pattern: &str, args: &mut Vec) -> Result { let mut ans = String::new(); let mut tokens = parse(pattern); let mut fin = true; for tok in tokens.iter_mut() { if tok.continue_() { fin = false; } ans += &tok.render_value(args)?; } if !args.is_empty() && !fin { if let Ok(s) = format(pattern, args) { ans += &s; } } Ok(ans) } fn arg_check(core: &mut ShellCore, args: &[String]) -> i32 { if args.len() < 2 || args[1] == "--help" || args[1] == "-v" && args.len() == 3 { let msg = "printf: usage: printf [-v var] format [arguments]".to_string(); error::print(&msg, core); return 2; } if args[1] == "-v" && args.len() == 2 { let msg = "printf: -v: option requires an argument".to_string(); error::print(&msg, core); let msg = "printf: usage: printf [-v var] format [arguments]".to_string(); error::print(&msg, core); return 2; } 0 } fn printf_v(core: &mut ShellCore, args: &mut Vec) -> i32 { if args[3] == "--" { args.remove(3); } let s = match format(&args[3], &mut args[4..].to_vec()) { Ok(ans) => ans, Err(e) => { let msg = String::from(&e); return super::error_(1, "printf", &msg, core); } }; if args[2].contains("[") { let mut f = Feeder::new(&(args[2].clone() + "=" + &s)); if let Ok(Some(mut a)) = Substitution::parse(&mut f, core, false, false) { if let Err(e) = a.eval(core, None, false) { let msg = String::from(&e); return super::error_(2, "printf", &msg, core); } } else { return 1; } return 0; } if let Err(e) = core.db.set_param(&args[2], &s, None) { let msg = String::from(&e); return super::error_(2, "printf", &msg, core); } 0 } pub fn printf(core: &mut ShellCore, args: &[String]) -> i32 { let mut args = args.to_owned(); match arg_check(core, &args) { 0 => {} n => return n, } if args[1] == "-v" { return printf_v(core, &mut args); } let s = match format(&args[1], &mut args[2..].to_vec()) { Ok(ans) => ans, Err(e) => { let msg = format!("printf: {e:?}"); error::print(&msg, core); return 1; } }; print!("{}", &s); stdout().flush().unwrap(); 0 } ================================================ FILE: src/core/builtins/pwd.rs ================================================ //SPDX-FileCopyrightText: 2023 Ryuichi Ueda //SPDX-FileCopyrightText: 2023 @caro@mi.shellgei.org //SPDX-License-Identifier: BSD-3-Clause use crate::ShellCore; pub fn pwd(core: &mut ShellCore, args: &[String]) -> i32 { if args.len() == 1 || &args[1][..1] != "-" { // $ pwd, $ pwd aaa return show_pwd(core, false); } match args[1].as_str() { //$pwd -L, pwd -P, pwd -aaaa "-P" => show_pwd(core, true), // シンボリックリンク名を解決して表示 "-L" => show_pwd(core, false), // シンボリックリンク名をそのまま表示(bash default) _ => { eprintln!("sush: pwd: {}: invalid option", &args[1]); eprintln!("pwd: usage: pwd [-LP]"); 1 } } } fn show_pwd(core: &mut ShellCore, physical: bool) -> i32 { if let Some(mut path) = core.get_current_directory() { if physical && path.is_symlink() { if let Ok(c) = path.canonicalize() { path = c; } } println!("{}", path.display()); return 0; } 1 } ================================================ FILE: src/core/builtins/read.rs ================================================ //SPDX-FileCopyrightText: 2024 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use super::error_; use crate::elements::substitution::variable::Variable; use crate::{arg, error, utils, ShellCore}; fn check_word_limit(word: &mut String, limit: &mut usize) -> bool { let mut pos = 0; for c in word.chars() { if *limit == 0 { let _ = word.split_off(pos); return true; } *limit -= 1; pos += c.len_utf8(); } false } pub fn read_( core: &mut ShellCore, args: &mut Vec, ignore_escape: bool, limit: &mut usize, delim: &String, ) -> i32 { let mut remaining = utils::read_line_stdin_unbuffered(delim).unwrap_or("".to_string()); if remaining.is_empty() { return 1; } let ifs = match core.db.exist("IFS") { true => core.db.get_param("IFS").unwrap(), false => " \t\n".to_string(), }; let mut tail_space = ifs .chars() .filter(|i| " \t\n".contains(*i)) .collect::(); tail_space += delim; args.remove(0); if args.is_empty() { args.push("REPLY".to_string()); } consume_ifs(&mut remaining, " \t", limit); while !args.is_empty() && !remaining.is_empty() && *limit != 0 { let mut word = match eat_word(core, &mut remaining, &ifs, ignore_escape, delim) { Some(w) => w, None => break, }; check_word_limit(&mut word, limit); if args.len() == 1 && *limit != 0 { let bkup = remaining.clone(); consume_ifs(&mut remaining, &ifs, limit); if remaining.is_empty() || remaining == "\n" { } else { word += &bkup; } } consume_tail_ifs(&mut word, &tail_space); if let Err(e) = Variable::parse_and_set(&args[0], &word, core) { return super::error_(1, "read", &String::from(&e), core); } args.remove(0); consume_ifs(&mut remaining, &ifs, limit); } 0 } pub fn read_a( core: &mut ShellCore, name: &str, ignore_escape: bool, limit: &mut usize, delim: &String, ) -> i32 { let mut remaining = utils::read_line_stdin_unbuffered(delim).unwrap_or("".to_string()); if remaining.is_empty() { return 1; } let ifs = match core.db.exist("IFS") { true => core.db.get_param("IFS").unwrap(), false => " \t\n".to_string(), }; let mut tail_space = ifs .chars() .filter(|i| " \t\n".contains(*i)) .collect::(); tail_space += delim; consume_ifs(&mut remaining, " \t", limit); let mut pos = 0; while !remaining.is_empty() { let mut word = match eat_word(core, &mut remaining, &ifs, ignore_escape, delim) { Some(w) => w, None => break, }; check_word_limit(&mut word, limit); consume_tail_ifs(&mut word, &tail_space); if let Err(e) = core.db.set_array_elem(name, &word, pos, None, false) { let msg = format!("{:?}", &e); error::print(&msg, core); return 1; } pos += 1; consume_ifs(&mut remaining, &ifs, limit); } 0 } pub fn read(core: &mut ShellCore, args: &[String]) -> i32 { if args.is_empty() { return 0; } let mut args = arg::dissolve_options(args); let r_opt = arg::consume_arg("-r", &mut args); let mut limit = usize::MAX; let limit_str = arg::consume_with_next_arg("-n", &mut args); let delim = match arg::consume_with_next_arg("-d", &mut args) { Some(c) => c, None => "\n".to_string(), }; if let Some(limit_str) = limit_str { match limit_str.parse::() { Ok(n) => limit = n, Err(_) => { let err = format!("{}: invalid number", &limit_str); return error_(1, "read", &err, core); } }; } if let Some(a) = arg::consume_with_next_arg("-a", &mut args) { return read_a(core, &a, r_opt, &mut limit, &delim); } read_(core, &mut args, r_opt, &mut limit, &delim) } pub fn eat_word( _core: &mut ShellCore, remaining: &mut String, ifs: &str, ignore_escape: bool, delim: &String, ) -> Option { let mut esc = false; let mut pos = 0; let mut escape_pos = vec![]; for c in remaining.chars() { if (esc || c == '\\') && !ignore_escape { esc = !esc; if esc { escape_pos.push(pos); } pos += c.len_utf8(); continue; } if ifs.contains(c) { break; } pos += c.len_utf8(); } if let Some(p) = escape_pos.last() { if p + 2 == remaining.len() && remaining.ends_with('\n') { remaining.pop(); remaining.pop(); let line = utils::read_line_stdin_unbuffered(delim).unwrap_or("".to_string()); if !line.is_empty() { *remaining += &line; return eat_word(_core, remaining, ifs, ignore_escape, delim); } } } let tail = remaining.split_off(pos); let mut ans = remaining.clone(); *remaining = tail; for p in escape_pos { ans.remove(p); } Some(ans) } pub fn consume_tail_ifs(remaining: &mut String, ifs: &str) { loop { if let Some(c) = remaining.chars().last() { if ifs.contains(c) { remaining.pop(); continue; } } break; } } pub fn consume_ifs(remaining: &mut String, ifs: &str, limit: &mut usize) { let special_ifs: Vec = ifs.chars().filter(|s| !" \t\n".contains(*s)).collect(); let mut pos = 0; let mut special_ifs_exist = false; for ch in remaining.chars() { if !ifs.contains(ch) || *limit == 0 { break; } if special_ifs.contains(&ch) { if special_ifs_exist { break; } special_ifs_exist = true; } pos += ch.len_utf8(); *limit -= 1; } let tail = remaining.split_off(pos); *remaining = tail; } ================================================ FILE: src/core/builtins/source.rs ================================================ //SPDX-FileCopyrightText: 2024 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::error::parse::ParseError; use crate::{file_check, Feeder, Script, ShellCore}; fn check_error(core: &mut ShellCore, args: &[String]) -> i32 { if core.db.flags.contains('r') && args[1].contains('/') { let msg = format!("{}: restricted", &args[1]); return super::error_(1, &args[0], &msg, core); } if args.len() < 2 { eprintln!("sush: source: filename argument required"); eprintln!("source: usage: source filename [arguments]"); return 2; } if file_check::is_dir(&args[1]) { eprintln!("sush: source: {}: is a directory", &args[1]); return 1; } 0 } pub fn source(core: &mut ShellCore, args: &[String]) -> i32 { let args = args.to_owned(); let check = check_error(core, &args); if check != 0 { return check; } let mut feeder = Feeder::new(""); if let Err(e) = feeder.set_file(&args[1]) { ParseError::Input(e).print(core); return 1; } let mut source = match core.db.get_vec("BASH_SOURCE", false) { Ok(s) => s, Err(e) => { e.print(core); return 1; } }; core.source_function_level += 1; core.source_files.push(args[1].to_string()); core.db.position_parameters.push(args[1..].to_vec()); source.insert(0, args[1].clone()); let _ = core.db.init_array("BASH_SOURCE", Some(source.clone()), None, false); feeder.main_feeder = true; while let Ok(()) = feeder.feed_line(core) { if core.return_flag { feeder.consume(feeder.len()); } match Script::parse(&mut feeder, core, false) { Ok(Some(mut s)) => { let _ = s.exec(core); } Err(e) => e.print(core), _ => {} } } source.remove(0); let _ = core.db.init_array("BASH_SOURCE", Some(source), None, false); core.db.position_parameters.pop(); core.source_function_level -= 1; core.source_files.pop(); core.return_flag = false; core.db.exit_status } ================================================ FILE: src/core/builtins/trap.rs ================================================ //SPDX-FileCopyrightText: 2023 Ryuichi Ueda //SPDX-FileCopyrightText: 2023 @caro@mi.shellgei.org //SPDX-License-Identifier: BSD-3-Clause use crate::error::exec::ExecError; use crate::signal; use crate::ShellCore; use nix::sys::signal::Signal; use signal_hook::iterator::Signals; use std::str::FromStr; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering::Relaxed; use std::sync::Arc; use std::{thread, time}; pub fn trap(core: &mut ShellCore, args: &[String]) -> i32 { let args = args.to_owned(); if args.len() == 1 { for e in &core.traplist { if e.0 == 0 { println!("trap -- '{}' EXIT", &e.1); } else if let Ok(s) = Signal::try_from(e.0) { println!("trap -- '{}' {}", &e.1, &s); } } return 0; } if args.len() < 3 { // TODO: print the list of trap entries if args.len() == 1 eprintln!("trap: usage: trap arg signal_spec ..."); return 2; } let forbiddens = Vec::from(signal_hook::consts::FORBIDDEN); let signals = match args_to_nums(&args[2..], &forbiddens) { Ok(v) => v, Err(e) => { e.print(core); return 1; } }; let mut exit = false; let mut valid_signals = vec![]; for n in &signals { if *n == 0 { exit = true; continue; } if let Ok(s) = TryFrom::try_from(*n) { signal::ignore(s); valid_signals.push(*n); continue; }; let msg = format!("trap: {n}: invalid signal specification"); return super::error_(1, &args[0], &msg, core); } if !valid_signals.is_empty() { for n in &valid_signals { core.traplist.push((*n, args[1].to_string())); } run_thread(valid_signals, &args[1], core); } if exit { core.traplist.push((0, args[1].to_string())); core.exit_script = args[1].clone(); } 0 } fn run_thread(signal_nums: Vec, script: &str, core: &mut ShellCore) { core.trapped .push((Arc::new(AtomicBool::new(false)), script.to_string())); let trap = Arc::clone(&core.trapped.last().unwrap().0); thread::spawn(move || { let mut signals = Signals::new(signal_nums.clone()).expect("sush(fatal): cannot prepare signal data"); loop { thread::sleep(time::Duration::from_millis(5)); for signal in signals.pending() { if signal_nums.contains(&signal) { trap.store(true, Relaxed); } } } }); } fn arg_to_num(arg: &str, forbiddens: &[i32]) -> Result { if arg == "EXIT" || arg == "0" { return Ok(0); } if let Ok(n) = Signal::from_str(arg) { return Ok(n as i32); } if let Ok(n) = Signal::from_str(&("SIG".to_owned() + arg)) { return Ok(n as i32); } if let Ok(n) = arg.parse::() { if forbiddens.contains(&n) { return Err(ExecError::Other(format!( "trap: {arg}: forbidden signal for trap" ))); } return Ok(n); } Err(ExecError::Other(format!( "trap: {arg}: invalid signal specification" ))) } fn args_to_nums(args: &[String], forbiddens: &[i32]) -> Result, ExecError> { let mut ans = vec![]; for a in args { let n = arg_to_num(a, forbiddens)?; ans.push(n); } Ok(ans) } ================================================ FILE: src/core/builtins/type_.rs ================================================ //SPDX-FileCopyrightText: 2024 Ryuichi Ueda //SPDX-FileCopyrightText: 2023 @caro@mi.shellgei.org //SPDX-License-Identifier: BSD-3-Clause use crate::utils::{arg, file}; use crate::{file_check, utils, ShellCore}; fn type_no_opt_sub(core: &mut ShellCore, com: &String) -> i32 { if core.shopts.query("expand_aliases") && core.db.has_array_value("BASH_ALIASES", com) { let alias = core.db.get_elem("BASH_ALIASES", com).unwrap(); println!("{} is aliased to `{}'", &com, &alias); return 0; } if utils::reserved(com) { println!("{} is a shell keyword", &com); return 0; } if core.db.functions.contains_key(com) { println!("{} is a function", &com); if let Some(val) = core.db.functions.get_mut(com) { val.pretty_print(0); }; return 0; } if core.builtins.contains_key(com) { println!("{com} is a shell builtin"); return 0; } if let Some(path) = file::search_command(com) { println!("{} is {}", com, &path); return 0; } if file_check::is_executable(com) { println!("{com} is {com}"); return 0; } let s = format!("{}: not found", &com); super::error_(1, "type", &s, core) } fn type_no_opt(core: &mut ShellCore, args: &[String]) -> i32 { let mut exit_status = 0; for a in args { exit_status += type_no_opt_sub(core, a); } if exit_status > 1 { exit_status = 1; } exit_status } fn type_t(core: &mut ShellCore, args: &[String]) -> i32 { let mut exit_status = 0; for a in args { exit_status += type_t_sub(core, a); } if exit_status > 1 { exit_status = 1; } exit_status } fn type_t_sub(core: &mut ShellCore, com: &String) -> i32 { if core.shopts.query("expand_aliases") && core.db.has_array_value("BASH_ALIASES", com) { println!("alias"); return 0; } if utils::reserved(com) { println!("keyword"); return 0; } if core.db.functions.contains_key(com) { println!("function"); return 0; } if core.builtins.contains_key(com) { println!("builtin"); return 0; } if file::search_command(com).is_some() || file_check::is_executable(com) { println!("file"); return 0; } 1 } fn type_p(core: &mut ShellCore, args: &[String]) -> i32 { let mut exit_status = 0; for a in args { exit_status += type_p_sub(core, a); } if exit_status > 1 { exit_status = 1; } exit_status } fn type_large_p(core: &mut ShellCore, args: &[String]) -> i32 { let mut exit_status = 0; for a in args { exit_status += type_large_p_sub(core, a); } if exit_status > 1 { exit_status = 1; } exit_status } fn type_p_sub(core: &mut ShellCore, com: &String) -> i32 { if core.db.has_array_value("BASH_ALIASES", com) || core.db.functions.contains_key(com) || utils::reserved(com) || core.builtins.contains_key(com) { return 0; } if let Ok(path) = core.db.get_elem("BASH_CMDS", com) { if !path.is_empty() { println!("{}", &path); return 0; } } if let Some(path) = file::search_command(com) { println!("{}", &path); return 0; } if file_check::is_executable(com) { println!("{com}"); return 0; } 1 } fn type_large_p_sub(core: &mut ShellCore, com: &String) -> i32 { let mut es = 1; if core.db.has_array_value("BASH_ALIASES", com) || core.db.functions.contains_key(com) || utils::reserved(com) || core.builtins.contains_key(com) { es = 0; } if let Some(path) = file::search_command(com) { println!("{}", &path); return 0; } if file_check::is_executable(com) { println!("{com}"); return 0; } es } pub fn type_(core: &mut ShellCore, args: &[String]) -> i32 { if args.len() < 2 { return 0; } let mut args = arg::dissolve_options(args); let t_option = arg::consume_arg("-t", &mut args); if t_option { if args.len() > 1 && args[1] == "--" { args.remove(1); } return type_t(core, &args[1..]); } let p_option = arg::consume_arg("-p", &mut args); if p_option { if args.len() > 1 && args[1] == "--" { args.remove(1); } return type_p(core, &args[1..]); } let large_p_option = arg::consume_arg("-P", &mut args); if large_p_option { if args[1] == "--" { args.remove(1); } return type_large_p(core, &args[1..]); } type_no_opt(core, &args[1..]) } ================================================ FILE: src/core/builtins/ulimit.rs ================================================ //SPDX-FileCopyrightText: 2025 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::{arg, ShellCore}; use nix::libc; use nix::sys::resource; use nix::sys::resource::{Resource, rlim_t}; fn items() -> &'static [(&'static str, &'static str, &'static str, Resource)] { &[ #[cfg(any(target_os = "linux"))] ( "real-time non-blocking time ", "microseconds", "-R", Resource::RLIMIT_RTTIME, ), ( "core file size ", "blocks", "-c", Resource::RLIMIT_CORE, ), ( "data seg size ", "kbytes", "-d", Resource::RLIMIT_DATA, ), #[cfg(any(target_os = "linux", target_os = "android"))] ( "scheduling priority ", "", "-e", Resource::RLIMIT_NICE, ), ( "file size ", "blocks", "-f", Resource::RLIMIT_FSIZE, ), #[cfg(any(target_os = "linux", target_os = "android"))] ( "pending signals ", "", "-i", Resource::RLIMIT_SIGPENDING, ), ( "max locked memory ", "kbytes", "-l", Resource::RLIMIT_MEMLOCK, ), ( "max memory size ", "kbytes", "-m", Resource::RLIMIT_RSS, ), ( "open files ", "", "-n", Resource::RLIMIT_NOFILE, ), #[cfg(any(target_os = "linux", target_os = "android"))] ( "pipe size ", "512 bytes", "-p", Resource::RLIMIT_SIGPENDING, ), //dummy #[cfg(any(target_os = "linux", target_os = "android"))] ( "POSIX message queues ", "bytes", "-q", Resource::RLIMIT_MSGQUEUE, ), #[cfg(any(target_os = "linux", target_os = "android"))] ( "real-time priority ", "", "-r", Resource::RLIMIT_RTPRIO, ), ( "stack size ", "kbytes", "-s", Resource::RLIMIT_STACK, ), ( "cpu time ", "seconds", "-t", Resource::RLIMIT_CPU, ), ( "max user processes ", "", "-u", Resource::RLIMIT_NPROC, ), #[cfg(not(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd")))] ( "virtual memory ", "kbytes", "-v", Resource::RLIMIT_AS, ), #[cfg(any(target_os = "linux", target_os = "android"))] ( "file locks ", "", "-x", Resource::RLIMIT_LOCKS, ), ] } fn print_items(args: &[String], soft: bool) -> i32 { for a in args { for (item, unit, opt, key) in items() { if a == opt { print_item(item, unit, opt, *key, soft); } } } 0 } fn print_item(item: &str, unit: &str, opt: &str, key: Resource, soft: bool) -> i32 { let (soft_limit, hard_limit) = resource::getrlimit(key).unwrap(); let mut v = if soft { soft_limit } else { hard_limit }; let mut infty = nix::sys::resource::RLIM_INFINITY; if item.starts_with("pipe size") { v = ((libc::PIPE_BUF as rlim_t) / 512) as rlim_t; } if unit.starts_with("kbytes") { v /= 1024; infty /= 1024; } let s = if v == infty { "unlimited" } else { &v.to_string() }; if unit == "" { println!("{}({}) {}", &item, &opt, &s); } else { println!("{}({}, {}) {}", &item, &unit, &opt, &s); } 0 } fn print_all(soft: bool) -> i32 { for (item, unit, opt, key) in items() { print_item(item, unit, opt, *key, soft); } 0 } fn set_limit(opt: &String, num: &String, soft: bool, hard: bool) -> i32 { let mut limit = match num.as_str() { "unlimited" => nix::sys::resource::RLIM_INFINITY, numstr => match numstr.parse::() { Ok(n) => n, Err(e) => { dbg!("{:?}", &e); return 1; } }, }; for (_, unit, opt2, key) in items() { if opt == opt2 { let (mut soft_limit, mut hard_limit) = resource::getrlimit(*key).unwrap(); if unit.starts_with("kbytes") { limit *= 1024; } if soft { soft_limit = limit; } if hard { hard_limit = limit; } match resource::setrlimit(*key, soft_limit, hard_limit) { Err(e) => { dbg!("{:?}", &e); return 1; } _ => return 0, } } } 0 } pub fn ulimit(_: &mut ShellCore, args: &[String]) -> i32 { let mut args = arg::dissolve_options(args); let mut soft = arg::consume_arg("-S", &mut args); let mut hard = arg::consume_arg("-H", &mut args); if args.iter().any(|a| a == "-a") { return print_all(!hard); } if args.len() > 2 && args[2].parse::().is_ok() { if !soft && !hard { soft = true; hard = true; } return set_limit(&args[1], &args[2], soft, hard); } print_items(&args, !hard) } ================================================ FILE: src/core/builtins/unset.rs ================================================ //SPDX-FileCopyrightText: 2024 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::{Feeder, ShellCore}; use crate::error::exec::ExecError; use crate::elements::expr::arithmetic::ArithmeticExpr; fn unset_all(core: &mut ShellCore, name: &str) -> Result { if ! core.shopts.query("localvar_unset") { core.db.unset(name, None, false)?; return Ok(0); } let mut scope = core.db.get_scope_num()-1; if scope <= 1 { core.db.unset(name, None, true)?; }else{ scope -= 1; core.db.unset(name, Some(scope), true)?; } Ok(0) } fn unset_var(core: &mut ShellCore, name: &str) -> Result { if ! core.shopts.query("localvar_unset") { core.db.unset_var(name, None, false)?; return Ok(0); } let mut scope = core.db.get_scope_num()-1; if scope <= 1 { core.db.unset_var(name, None, true)?; }else{ scope -= 1; core.db.unset_var(name, Some(scope), true)?; } Ok(0) } fn unset_nameref(core: &mut ShellCore, name: &str) -> i32 { if ! core.shopts.query("localvar_unset") { let _ = core.db.unset_nameref(name, None); return 0; } let mut scope = core.db.get_scope_num()-1; if scope <= 1 { let _ = core.db.unset_nameref(name, None); }else{ scope -= 1; let _ = core.db.unset_nameref(name, Some(scope)); } 0 } fn unset_function(core: &mut ShellCore, name: &str) -> i32 { core.db.unset_function(name); 0 } fn unset_one(core: &mut ShellCore, args: &mut Vec) -> i32 { match args[1].as_ref() { "-f" => { if args.len() > 2 { let name = args.remove(2); return unset_function(core, &name); } } "-v" => { if args.len() > 2 { let name = args.remove(2); if let Err(e) = unset_var(core, &name) { return super::error(1, &args[0], &e, core); }else{ return 0; } } } "-n" => { if args.len() > 2 { let name = args.remove(2); return unset_nameref(core, &name); } } name => { let name = name.to_string(); args.remove(1); if !name.contains("[") { if let Err(e) = unset_all(core, &name) { return super::error(1, &args[0], &e, core); }else{ return 0; } } let pos = name.find("[").unwrap(); let mut name = name.clone(); let mut index = name.split_off(pos); if !index.ends_with("]") { let msg = format!("{}: invalid variable", &name); return super::error_(1, &args[0], &msg, core); } index.remove(0); index.pop(); let mut index = index; if core.db.is_array(&name) { if let Err(_) = index.parse::() { let mut f = Feeder::new(&index); match ArithmeticExpr::parse(&mut f, core, false, "[") { Ok(Some(mut v)) => { if !f.is_empty() { let e = ExecError::ArrayIndexInvalid(index.to_string()); return super::error(1, &args[0], &e, core); } if let Ok(n) = v.eval(core) { index = n; } }, _ => { let e = ExecError::ArrayIndexInvalid(index.to_string()); return super::error(1, &args[0], &e, core); }, } } } if let Err(e) = core.db.unset_array_elem(&name, &index) { return super::error_(1, &args[0], &String::from(&e), core); } return 0; } } 0 } pub fn unset(core: &mut ShellCore, args: &[String]) -> i32 { let mut args = args.to_owned(); let mut exit_status = 0; loop { if args.len() < 2 { break; } if (args[1] == "-v" || args[1] == "-f" || args[1] == "-n") && args.len() == 2 { break; } exit_status = unset_one(core, &mut args); } exit_status } ================================================ FILE: src/core/builtins/variable/print.rs ================================================ //SPDX-FileCopyrightText: 2025 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::{builtins, ShellCore}; use crate::elements::substitution::Substitution; use crate::utils::arg; fn format_options(name: &String, core: &mut ShellCore) -> String { let mut opts: Vec = core.db.get_flags(name).chars().collect(); opts.sort(); let ans: String = opts.into_iter().collect(); match ans.len() { 0 => "--".to_string(), _ => "-".to_owned() + &ans, } } fn drop_by_args(core: &mut ShellCore, names: &mut Vec, args: &[String]) { for flag in ['i', 'a', 'A', 'r', 'x', 'u', 'n', 'l'] { let opt = "-".to_owned() + &flag.to_string(); if arg::has_option(&opt, args) { names.retain(|n| core.db.has_flag(n, flag)); } } } fn output(core: &mut ShellCore, name: &String, args: &[String]) { let mut options = format_options(name, core); if core.options.query("posix") { options.retain(|e| e != 'r'); } match core.options.query("posix") { false => print!("declare {options} "), true => print!("{} {} ", &args[0], options), }; core.db.print_for_declare(name); } fn all_params(core: &mut ShellCore, args: &[String]) -> i32 { let mut names = core.db.get_param_keys(); drop_by_args(core, &mut names, args); names.iter().for_each(|n| {output(core, n, args); }); 0 } fn all_functions(core: &mut ShellCore, args: &[String]) -> i32 { if args.len() != 2 { return 1; } let mut names = core.db.get_func_keys(); names.sort(); if names.iter().all(|n| core.db.print_func(n)) { return 0; } 1 } pub(super) fn names_match(core: &mut ShellCore, names: &mut Vec, args: &[String]) -> i32 { drop_by_args(core, names, args); for n in names { if ! core.db.exist(n) && ! core.db.exist_nameref(n) { return builtins::error_(1, n, "not found", core); } output(core, n, args); } 0 } pub(super) fn f_option(core: &mut ShellCore, args: &[String], subs: &mut [Substitution]) -> i32 { if subs.is_empty() { return all_functions(core, &args); } if args.len() != 2 { return 1; } let mut names: Vec = subs. iter() .map(|s| s.left_hand.name.clone()) .collect(); names.sort(); if names.iter().all(|n| core.db.print_func(n)) { return 0; } 1 } pub(super) fn args_match(core: &mut ShellCore, args: &[String]) -> i32 { if args.len() <= 1 { core.db.print_params_and_funcs(); return 0; } all_params(core, &args) } ================================================ FILE: src/core/builtins/variable/set_value.rs ================================================ //SPDX-FileCopyrightText: 2025 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::ShellCore; use crate::elements::substitution::Substitution; use crate::error::exec::ExecError; use crate::utils::arg; fn set_options_pre(core: &mut ShellCore, name: &String, scope: usize, args: &[String]) { if arg::has_option("-x", args) { core.db.set_flag(name, 'x', scope); }else if arg::has_option("+x", args) { core.db.unset_flag(name, 'x', scope); } if arg::has_option("-n", args) { core.db.set_flag_nameref(name, 'n', scope); }else if arg::has_option("+n", args) { core.db.unset_flag_nameref(name, 'n', scope); } if arg::has_option("-i", args) { core.db.set_flag(name, 'i', scope); }else if arg::has_option("+i", args) { core.db.unset_flag(name, 'i', scope); } if arg::has_option("-l", args) { core.db.unset_flag(name, 'u', scope); core.db.set_flag(name, 'l', scope); }else if arg::has_option("+l", args) { core.db.unset_flag(name, 'l', scope); } if arg::has_option("-u", args) { core.db.unset_flag(name, 'l', scope); core.db.set_flag(name, 'u', scope); }else if arg::has_option("+u", args) { core.db.unset_flag(name, 'u', scope); } } fn set_options_post(core: &mut ShellCore, name: &String, scope: usize, args: &[String]) { if arg::has_option("-r", args) { core.db.set_flag(&name, 'r', scope); } } fn readonly_check(core: &mut ShellCore, name: &str) -> Result<(), ExecError> { if core.db.is_readonly(&name) { return Err(ExecError::VariableReadOnly(name.to_string())); } Ok(()) } fn array_to_element_check(sub: &mut Substitution) -> Result<(), ExecError> { if let Some(r) = sub.right_hand.as_mut() { if sub.left_hand.index.is_some() && r.text.starts_with("(") { let msg = format!("{}: cannot assign list to array member", sub.left_hand.text); return Err(ExecError::Other(msg)); } } Ok(()) } fn check_global_option(core: &mut ShellCore, args: &[String], name: &str, scope: usize) -> usize { if arg::has_option("-g", args) && scope != 0 { let _ = core.db.unset(&name, None, false); return 0; } scope } fn eval(core: &mut ShellCore, args: &[String], sub: &mut Substitution, name: &str, scope: usize) -> Result<(), ExecError> { if arg::has_option("-n", args) { sub.reset_nameref = true; } if sub.right_hand.is_some() { return sub.eval(core, Some(scope), true); } let change_type = (!core.db.is_array(&name) && arg::has_option("-a", args)) || (!core.db.is_assoc(&name) && arg::has_option("-A", args)); if !core.db.exist_l(&name, scope) || change_type { sub.left_hand.init_variable(core, Some(scope), &mut args.to_vec())?; } Ok(()) } pub(super) fn exec(core: &mut ShellCore, sub: &mut Substitution, args: &[String], scope: usize) -> Result<(), ExecError> { let name = sub.left_hand.name.clone(); readonly_check(core, &name)?; if arg::has_option("-n", args) { if sub.left_hand.index.is_some() || core.db.is_array(&sub.left_hand.name) || core.db.is_assoc(&sub.left_hand.name) { return Err(ExecError::RefCannotBeArray(sub.left_hand.text.clone())); } } array_to_element_check(sub)?; let scope = check_global_option(core, args, &name, scope); if ( arg::has_option("+i", args) || arg::has_option("-n", args) ) && core.db.has_flag_scope(&name, 'i', scope) { core.db.int_to_str_type(&name, scope)?; } let arg_indicate_array = arg::has_option("-A", args) || arg::has_option("-a", args); if arg_indicate_array && !core.db.exist(&name) && !core.db.exist_nameref(&name) { sub.left_hand.init_variable(core, Some(scope), &mut args.to_vec())?; } if let Some(r) = sub.right_hand.as_mut() { let right_is_array = ["(", "'(", "\"("].iter().any(|e| r.text.starts_with(e)); if arg_indicate_array && right_is_array { sub.left_hand.index = None; } } let already_array = core.db.is_array(&name) || core.db.is_assoc(&name); let subs_elem_quoted_string = match sub.right_hand.as_mut() { Some(r) => sub.left_hand.index.is_some() && (r.text.starts_with("'") || r.text.starts_with("\"")), _ => false, }; if arg_indicate_array || (already_array && args[0] == "declare") { sub.quoted = false; //^ Bash bug??? } let treat_as_export = core.db.has_flag(&name, 'x') || arg::has_option("-x", args); if sub.right_hand.is_some() && already_array && !subs_elem_quoted_string && (!treat_as_export || arg_indicate_array) { sub.reparse(core)?; } set_options_pre(core, &name, scope, args); let res = eval(core, args, sub, &name, scope); set_options_post(core, &name, scope, args); if arg::has_option("-n", args) { if res.is_err() { core.db.unset_nameref(&name, Some(scope))?; } } res } ================================================ FILE: src/core/builtins/variable.rs ================================================ //SPDX-FileCopyrightText: 2025 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause mod print; mod set_value; use crate::elements::substitution::Substitution; use crate::error::exec::ExecError; use crate::utils::arg; use crate::{env, ShellCore}; pub fn local(core: &mut ShellCore, args: &[String], subs: &mut [Substitution]) -> i32 { let args = args.to_owned(); let scope = if core.db.get_scope_num() > 2 { core.db.get_scope_num() - 2 //The last element of data.parameters is for local itself. } else { let e = &ExecError::ValidOnlyInFunction; return super::error(1, &args[0], e, core); }; if core.shopts.query("localvar_inherit") { subs.into_iter().for_each(|e| e.localvar_inherit(core) ); } for sub in subs.iter_mut() { if let Err(e) = set_value::exec(core, sub, &args, scope) { e.print(core); return 1; } } 0 } pub fn declare(core: &mut ShellCore, args: &[String], subs: &mut [Substitution]) -> i32 { let mut args = arg::dissolve_options(args); arg::consume_arg("--", &mut args); if arg::has_option("-f", &args) { return print::f_option(core, &args, subs); }else if subs.is_empty() { return print::args_match(core, &mut args); }else if arg::consume_arg("-p", &mut args) { //declare -p hoge let mut names = subs.iter().map(|s| s.text.clone()).collect(); return print::names_match(core, &mut names, &args); } let scope = core.db.get_scope_num() - 2; for sub in subs { if let Err(e) = set_value::exec(core, sub, &args, scope) { return super::error(1, &args[0], &e, core); } } 0 } pub fn export(core: &mut ShellCore, args: &[String], subs: &mut [Substitution]) -> i32 { let mut args = args.to_owned(); for sub in subs.iter_mut() { let scope = core.db.get_scope_pos(&sub.left_hand.name).unwrap_or(0); args.push("-x".to_string()); if let Err(e) = set_value::exec(core, sub, &args, scope) { e.print(core); return 1; } match core.db.get_param(&sub.left_hand.name) { Ok(v) => unsafe{env::set_var(&sub.left_hand.name, v)}, Err(e) => { e.print(core); return 1; } } } 0 } pub fn readonly(core: &mut ShellCore, args: &[String], subs: &mut [Substitution]) -> i32 { let args = arg::dissolve_options(args); if subs.is_empty() { let mut args = args.to_vec(); args.push("-r".to_string()); return print::args_match(core, &mut args); } for sub in subs { if sub.left_hand.index.is_some() { let e = ExecError::VariableInvalid(sub.left_hand.text.clone()); return super::error(1, &args[0], &e, core); } let scope = core.db.get_scope_pos(&sub.left_hand.name).unwrap_or(0); if let Err(e) = set_value::exec(core, sub, &args, scope) { return super::error(1, &args[0], &e, core); } core.db.set_flag(&sub.left_hand.name, 'r', scope); } 0 } ================================================ FILE: src/core/builtins.rs ================================================ //SPDX-FileCopyrightText: 2024 Ryuichi Ueda //SPDX-FileCopyrightText: 2023 @caro@mi.shellgei.org //SPDX-License-Identifier: BSD-3-Clause mod alias; mod caller; mod cd; mod command; pub mod compgen; pub mod complete; mod compopt; mod echo; mod exec; mod getopts; mod hash; mod history; mod job_commands; mod loop_control; pub mod option; pub mod variable; mod printf; mod pwd; mod read; pub mod source; mod trap; mod type_; #[cfg(not(target_os = "macos"))] mod ulimit; mod unset; use crate::elements::expr::arithmetic::ArithmeticExpr; use crate::error::exec::ExecError; use crate::error::parse::ParseError; use crate::{exit, Feeder, Script, ShellCore}; use std::io::Write; use std::process::Command; pub fn error_(exit_status: i32, name: &str, msg: &str, core: &mut ShellCore) -> i32 { let shellname = core.db.get_param("0").unwrap(); if core.db.flags.contains('i') { eprintln!("{}: {}: {}", &shellname, name, msg); } else { let lineno = core.db.get_param("LINENO").unwrap_or("".to_string()); eprintln!("{}: line {}: {}: {}", &shellname, &lineno, name, msg); } exit_status } pub fn error(exit_status: i32, name: &str, err: &ExecError, core: &mut ShellCore) -> i32 { error_(exit_status, name, &String::from(err), core) } pub fn run_external(core: &mut ShellCore, args: &[String], err_msg_cond: fn(i32) -> bool) -> i32 { match Command::new(&args[0]).args(args[1..].to_vec()).output() { Ok(com) => { let exit_status = com.status.code().unwrap_or(127); if ! com.stdout.is_empty() { let _ = std::io::stdout().write_all(&com.stdout); } if ! err_msg_cond(exit_status) { return exit_status; } let shellname = core.db.get_param("0").unwrap(); eprint!("{}: ", &shellname); if ! core.db.flags.contains('i') { let lineno = core.db.get_param("LINENO").unwrap_or("".to_string()); eprint!("line {}: ", &lineno); } let _ = std::io::stderr().write_all(&com.stderr); exit_status }, _ => 127 } } impl ShellCore { pub fn set_builtins(&mut self) { self.builtins.insert(":".to_string(), true_); self.builtins.insert("alias".to_string(), alias::alias); self.builtins.insert("bg".to_string(), job_commands::bg); self.builtins.insert("bind".to_string(), bind); self.builtins .insert("break".to_string(), loop_control::break_); self.builtins .insert("builtin".to_string(), command::builtin); self.builtins.insert("cd".to_string(), cd::cd); self.builtins.insert("caller".to_string(), caller::caller); self.builtins .insert("command".to_string(), command::command); self.builtins .insert("compgen".to_string(), compgen::compgen); self.builtins .insert("complete".to_string(), complete::complete); self.builtins .insert("compopt".to_string(), compopt::compopt); self.builtins .insert("continue".to_string(), loop_control::continue_); self.builtins.insert("debug".to_string(), debug); self.builtins .insert("disown".to_string(), job_commands::disown); self.builtins.insert("echo".to_string(), echo::echo); self.builtins.insert("eval".to_string(), eval); self.builtins.insert("exec".to_string(), exec::exec); self.builtins.insert("exit".to_string(), exit); self.builtins.insert("false".to_string(), false_); self.builtins.insert("fg".to_string(), job_commands::fg); self.builtins .insert("getopts".to_string(), getopts::getopts); self.builtins.insert("hash".to_string(), hash::hash); self.builtins .insert("history".to_string(), history::history); self.builtins.insert("jobs".to_string(), job_commands::jobs); self.builtins.insert("kill".to_string(), job_commands::kill); self.builtins.insert("let".to_string(), let_); self.builtins.insert("printf".to_string(), printf::printf); self.builtins.insert("pwd".to_string(), pwd::pwd); self.builtins.insert("read".to_string(), read::read); self.builtins .insert("return".to_string(), loop_control::return_); self.builtins.insert("set".to_string(), option::set); self.builtins.insert("trap".to_string(), trap::trap); self.builtins.insert("type".to_string(), type_::type_); self.builtins.insert("shift".to_string(), option::shift); self.builtins.insert("shopt".to_string(), option::shopt); //if file::search_command("ulimit").is_none() { #[cfg(not(target_os = "macos"))] self.builtins.insert("ulimit".to_string(), ulimit::ulimit); /* #[cfg(target_os = "macos")] self.builtins.insert("ulimit".to_string(), ulimit_mac::ulimit); }*/ self.builtins.insert("unalias".to_string(), alias::unalias); self.builtins.insert("unset".to_string(), unset::unset); self.builtins.insert("source".to_string(), source::source); self.builtins.insert(".".to_string(), source::source); self.builtins.insert("true".to_string(), true_); self.builtins.insert("test".to_string(), test); self.builtins.insert("[".to_string(), test); self.builtins.insert("wait".to_string(), job_commands::wait); self.subst_builtins .insert("export".to_string(), variable::export); self.subst_builtins .insert("readonly".to_string(), variable::readonly); self.subst_builtins .insert("typeset".to_string(), variable::declare); self.subst_builtins .insert("declare".to_string(), variable::declare); self.subst_builtins .insert("local".to_string(), variable::local); } } pub fn eval(core: &mut ShellCore, args: &[String]) -> i32 { let mut args = args.to_owned(); args.remove(0); if !args.is_empty() && args[0] == "--" { args.remove(0); } let script = args.join(" "); let mut feeder = Feeder::new(&script); let lineno = match core.db.get_param("LINENO") { Ok(s) => s.parse::().unwrap_or_default(), _ => 0, }; feeder.lineno += lineno - 1; match Script::parse(&mut feeder, core, false) { Ok(Some(mut s)) => { core.eval_level += 1; let _ = s.exec(core); core.eval_level -= 1; } Err(ParseError::UnexpectedSymbol(t)) => { let lineno = core.db.get_param("LINENO").unwrap_or("0".to_string()); let com = &core.db.position_parameters[0][0]; eprintln!( "{}: eval: line {}: syntax error near unexpected token `{}'", com, &lineno, &t ); eprintln!("{}: eval: line {}: `{}'", com, &lineno, &script); return 2; } Err(e) => e.print(core), _ => {} } core.db.exit_status } pub fn exit(core: &mut ShellCore, args: &[String]) -> i32 { if core.db.flags.contains('i') { eprintln!("exit"); } if args.len() > 1 { match &args[1].parse::() { Ok(n) => core.db.exit_status = *n, _ => core.db.exit_status = 1, } } exit::normal(core) } pub fn false_(_: &mut ShellCore, _: &[String]) -> i32 { 1 } pub fn true_(_: &mut ShellCore, _: &[String]) -> i32 { 0 } pub fn bind(_: &mut ShellCore, _: &[String]) -> i32 { 0 } pub fn debug(_: &mut ShellCore, _: &[String]) -> i32 { // let pos = core.db.get_scope_pos("words").unwrap(); // // dbg!("{:?}", &core.db.params.len()); // dbg!("{:?}", &pos); // dbg!("{:?}", &core.db.params[pos].get("words")); 0 } pub fn let_(core: &mut ShellCore, args: &[String]) -> i32 { let mut last_result = 0; core.valid_assoc_expand_once = true; for a in &args[1..] { match ArithmeticExpr::parse(&mut Feeder::new(&a.replace("$", "\\$")), core, false, "") { Ok(Some(mut a)) => match a.eval(core) { Ok(s) => last_result = if s == "0" { 1 } else { 0 }, Err(e) => { core.valid_assoc_expand_once = false; return error(1, &args[0], &e, core); } }, Ok(None) => { core.valid_assoc_expand_once = false; return error_(1, &args[0], "expression expected", core); } Err(e) => { core.valid_assoc_expand_once = false; return error(1, &args[0], &From::from(e), core); } } } core.valid_assoc_expand_once = false; last_result } pub fn test(core: &mut ShellCore, args: &[String]) -> i32 { /* difference between the builtin test and the external command */ if (args.len() == 5 && args[0] == "[" && args[4] == "]") || (args.len() == 4 && args[0] == "test") { if args[2] == "=" { if args[1] == args[3] { return 0; }else if args[1] != args[3] { return 1; } } } run_external(core, args, |es| es > 1) /* /* call the external test command */ match Command::new(&args[0]).args(args[1..].to_vec()).output() { Ok(com) => { let exit_status = com.status.code().unwrap_or(127); if exit_status > 1 { let msg = String::from_utf8(com.stderr).unwrap_or("".to_string()); let shellname = core.db.get_param("0").unwrap(); if core.db.flags.contains('i') { eprintln!("{}: {}", &shellname, msg); } else { let lineno = core.db.get_param("LINENO").unwrap_or("".to_string()); eprintln!("{}: line {}: {}", &shellname, &lineno, msg); } } exit_status }, _ => 127 }*/ } ================================================ FILE: src/core/completion.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use std::collections::HashMap; #[derive(Debug, Clone, Default)] pub struct Completion { pub entries: HashMap, pub current: CompletionEntry, pub default_function: String, } #[derive(Debug, Clone, Default)] pub struct CompletionEntry { pub function: String, pub o_options: Vec, pub action: String, pub options: HashMap, pub large_w_cands: String, } ================================================ FILE: src/core/database/data/array.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use super::{case_change, Data}; use crate::error::exec::ExecError; use crate::utils; use std::collections::HashMap; #[derive(Debug, Clone)] pub struct ArrayData { pub body: HashMap, pub flags: String, } impl From>> for ArrayData { fn from(v: Option>) -> Self { let mut ans = Self::new(); v.unwrap().into_iter().enumerate().for_each(|(i, e)| { ans.body.insert(i, e); }); ans } } impl Data for ArrayData { fn boxed_clone(&self) -> Box { Box::new(self.clone()) } fn get_fmt_string(&mut self) -> String { self._get_fmt_string() } fn _get_fmt_string(&self) -> String { let mut formatted = "(".to_string(); for i in self.keys() { let ansi = utils::to_ansi_c(&self.body[&i]); if ansi == self.body[&i] { formatted += &format!("[{}]=\"{}\" ", i, &ansi.replace("$", "\\$")); } else { formatted += &format!("[{}]={} ", i, &ansi); } } if formatted.ends_with(" ") { formatted.pop(); } formatted += ")"; formatted } fn clear(&mut self) { self.body.clear(); } fn set_as_single(&mut self, name: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; let mut value = value.to_string(); case_change(&self.flags, &mut value); self.body.insert(0, value); Ok(()) } fn append_as_single(&mut self, name: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; let mut value = if let Some(v) = self.body.get(&0) { v.to_owned() + value } else { value.to_string() }; case_change(&self.flags, &mut value); self.body.insert(0, value); Ok(()) } fn set_as_array(&mut self, name: &str, key: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; let n = self.index_of(key)?; let mut value = value.to_string(); case_change(&self.flags, &mut value); self.body.insert(n, value); Ok(()) } fn append_to_array_elem(&mut self, name: &str, key: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; let n = self.index_of(key)?; let mut value = if let Some(v) = self.body.get(&n) { v.to_owned() + value } else { value.to_string() }; case_change(&self.flags, &mut value); self.body.insert(n, value); Ok(()) } fn get_as_array(&mut self, key: &str, ifs: &str) -> Result { if key == "@" { return Ok(self.values().join(" ")); } if key == "*" { return Ok(self.values().join(ifs)); } let n = self.index_of(key)?; Ok(self.body.get(&n).unwrap_or(&"".to_string()).clone()) } fn get_vec_from(&mut self, pos: usize, skip_non: bool) -> Result, ExecError> { if self.body.is_empty() { return Ok(vec![]); } let keys = self.keys(); let max = *keys.iter().max().unwrap(); let mut ans = vec![]; for i in pos..(max + 1) { match self.body.get(&i) { Some(s) => ans.push(s.clone()), None => { if !skip_non { ans.push("".to_string()); } } } } Ok(ans) } fn get_all_indexes_as_array(&mut self) -> Result, ExecError> { Ok(self.keys().iter().map(|k| k.to_string()).collect()) } fn get_as_single(&mut self) -> Result { self.body .get(&0) .map(|v| Ok(v.clone())) .ok_or(ExecError::Other("No entry".to_string()))? } fn is_array(&self) -> bool { true } fn len(&mut self) -> usize { self.body.len() } fn has_key(&mut self, key: &str) -> Result { if key == "@" || key == "*" { return Ok(true); } let n = self.index_of(key)?; Ok(self.body.contains_key(&n)) } fn index_based_len(&mut self) -> usize { match self.body.iter().map(|e| e.0).max() { Some(n) => *n + 1, None => 0, } } fn elem_len(&mut self, key: &str) -> Result { if key == "@" || key == "*" { return Ok(self.len()); } let n = self.index_of(key)?; let s = self.body.get(&n).unwrap_or(&"".to_string()).clone(); Ok(s.chars().count()) } fn remove_elem(&mut self, key: &str) -> Result<(), ExecError> { if key == "*" || key == "@" { self.body.clear(); return Ok(()); } let index = self.index_of(key)?; self.body.remove(&index); Ok(()) } fn set_flag(&mut self, flag: char) { if ! self.flags.contains(flag) { self.flags.push(flag); } } fn unset_flag(&mut self, flag: char) { self.flags.retain(|e| e != flag); } fn get_flags(&mut self) -> &str { &self.flags } } impl ArrayData { pub fn new() -> Self { Self { body: HashMap::new(), flags: "a".to_string(), } } pub fn values(&self) -> Vec { let mut keys: Vec = self.body.iter().map(|e| *e.0).collect(); keys.sort(); keys.iter().map(|i| self.body[i].clone()).collect() } pub fn keys(&self) -> Vec { let mut keys: Vec = self.body.iter().map(|e| *e.0).collect(); keys.sort(); keys } fn index_of(&mut self, key: &str) -> Result { let mut index = match key.parse::() { Ok(i) => i, _ => return Err(ExecError::ArrayIndexInvalid(key.to_string())), }; if index >= 0 { return Ok(index as usize); } let keys = self.keys(); let max = match keys.iter().max() { Some(n) => *n as isize, None => -1, }; index += max + 1; if index < 0 { return Err(ExecError::ArrayIndexInvalid(key.to_string())); } Ok(index as usize) } } ================================================ FILE: src/core/database/data/array_int.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use super::array::ArrayData; use super::Data; use crate::error::exec::ExecError; use std::collections::HashMap; #[derive(Debug, Clone)] pub struct IntArrayData { body: HashMap, pub flags: String, } impl Data for IntArrayData { fn boxed_clone(&self) -> Box { Box::new(self.clone()) } fn get_fmt_string(&mut self) -> String { self._get_fmt_string() } fn _get_fmt_string(&self) -> String { let mut formatted = "(".to_string(); for i in self.keys() { formatted += &format!("[{}]=\"{}\" ", i, &self.body[&i]); } if formatted.ends_with(" ") { formatted.pop(); } formatted += ")"; formatted } fn clear(&mut self) { self.body.clear(); } fn has_key(&mut self, key: &str) -> Result { if key == "@" || key == "*" { return Ok(true); } let n = self.index_of(key)?; Ok(self.body.contains_key(&n)) } fn set_as_single(&mut self, name: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; let n = super::to_int(value)?; self.body.insert(0, n); Ok(()) } fn append_as_single(&mut self, name: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; let n = match value.parse::() { Ok(n) => n, Err(e) => return Err(ExecError::Other(e.to_string())), }; if let Some(v) = self.body.get(&0) { self.body.insert(0, v + n); } else { self.body.insert(0, n); } Ok(()) } fn set_as_array(&mut self, name: &str, key: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; let key = self.index_of(key)?; let n = super::to_int(value)?; self.body.insert(key, n); Ok(()) } fn append_to_array_elem(&mut self, name: &str, key: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; let key = self.index_of(key)?; let n = super::to_int(value)?; if let Some(prev) = self.body.get(&key) { self.body.insert(key, prev + n); } else { self.body.insert(key, n); } Ok(()) } fn get_as_array(&mut self, key: &str, _: &str) -> Result { if key == "@" { return Ok(self.values().join(" ")); } /* if key == "@" { return Ok(self.values().join(ifs)); }*/ let n = key .parse::() .map_err(|_| ExecError::ArrayIndexInvalid(key.to_string()))?; Ok(self.body.get(&n).unwrap_or(&0).to_string()) } fn get_all_as_array(&mut self, skip_none: bool) -> Result, ExecError> { if self.body.is_empty() { return Ok(vec![]); } let keys = self.keys(); let max = *keys.iter().max().unwrap(); let mut ans = vec![]; for i in 0..(max + 1) { match self.body.get(&i) { Some(s) => ans.push(s.to_string()), None => { if !skip_none { ans.push("".to_string()); } } } } Ok(ans) } fn get_vec_from(&mut self, pos: usize, skip_non: bool) -> Result, ExecError> { if self.body.is_empty() { return Ok(vec![]); } let keys = self.keys(); let max = *keys.iter().max().unwrap(); let mut ans = vec![]; for i in pos..(max + 1) { match self.body.get(&i) { Some(s) => ans.push(s.to_string()), None => { if !skip_non { ans.push("".to_string()); } } } } Ok(ans) } fn get_all_indexes_as_array(&mut self) -> Result, ExecError> { Ok(self.keys().iter().map(|k| k.to_string()).collect()) } fn get_as_single(&mut self) -> Result { self.body .get(&0) .map(|v| Ok(v.to_string())) .ok_or(ExecError::Other("No entry".to_string()))? } fn get_str_type(&self) -> Box { let mut hash = HashMap::new(); for d in &self.body { hash.insert(*d.0, d.1.to_string()); } let mut str_d = ArrayData { body: hash, flags: self.flags.clone(), }; str_d.unset_flag('i'); Box::new(str_d) } fn is_array(&self) -> bool { true } fn len(&mut self) -> usize { self.body.len() } fn elem_len(&mut self, key: &str) -> Result { if key == "@" || key == "*" { return Ok(self.len()); } let n = key .parse::() .map_err(|_| ExecError::ArrayIndexInvalid(key.to_string()))?; let s = self.body.get(&n).unwrap_or(&0).to_string(); Ok(s.chars().count()) } fn remove_elem(&mut self, key: &str) -> Result<(), ExecError> { if key == "*" || key == "@" { self.body.clear(); return Ok(()); } if let Ok(n) = key.parse::() { self.body.remove(&n); return Ok(()); } Err(ExecError::Other("invalid index".to_string())) } fn set_flag(&mut self, flag: char) { if ! self.flags.contains(flag) { self.flags.push(flag); } } fn unset_flag(&mut self, flag: char) { self.flags.retain(|e| e != flag); } fn has_flag(&mut self, flag: char) -> bool { if flag == 'i' { return true; } self.flags.contains(flag) } fn get_flags(&mut self) -> &str { &self.flags } } impl IntArrayData { pub fn new() -> Self { Self { body: HashMap::new(), flags: "ai".to_string(), } } pub fn values(&self) -> Vec { let mut keys: Vec = self.body.iter().map(|e| *e.0).collect(); keys.sort(); keys.iter().map(|i| self.body[i].to_string()).collect() } pub fn keys(&self) -> Vec { let mut keys: Vec = self.body.iter().map(|e| *e.0).collect(); keys.sort(); keys } fn index_of(&mut self, key: &str) -> Result { let mut index = match key.parse::() { Ok(i) => i, _ => return Err(ExecError::ArrayIndexInvalid(key.to_string())), }; if index >= 0 { return Ok(index as usize); } let keys = self.keys(); let max = match keys.iter().max() { Some(n) => *n as isize, None => -1, }; index += max + 1; if index < 0 { return Err(ExecError::ArrayIndexInvalid(key.to_string())); } Ok(index as usize) } } ================================================ FILE: src/core/database/data/array_ondemand.rs ================================================ //SPDXFileCopyrightText: 2025 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use crate::utils; use super::{Data, ExecError}; #[derive(Debug, Clone)] pub struct OnDemandArray { pub values: fn() -> Vec, pub flags: String, } impl Data for OnDemandArray { fn boxed_clone(&self) -> Box { Box::new(self.clone()) } fn _get_fmt_string(&self) -> String { return "*********".to_string() } fn get_fmt_string(&mut self) -> String { let mut formatted = "(".to_string(); for (i, v) in (self.values)().into_iter().enumerate() { let ansi = utils::to_ansi_c(&v); if ansi == v { formatted += &format!("[{}]=\"{}\" ", i, &ansi.replace("$", "\\$")); } else { formatted += &format!("[{}]={} ", i, &ansi); } } if formatted.ends_with(" ") { formatted.pop(); } formatted += ")"; formatted } fn get_as_array(&mut self, key: &str, ifs: &str) -> Result { if key == "@" { return Ok((self.values)().join(" ")); } if key == "*" { return Ok((self.values)().join(ifs)); } let index = self.index_of(key)?; let vs = (self.values)(); if index < vs.len() { return Ok(vs[index].clone()); } Ok("".to_string()) } fn get_vec_from(&mut self, pos: usize, _: bool) -> Result, ExecError> { let vs = (self.values)(); if pos < vs.len() { return Ok(vs[pos..].to_vec()); } Ok(vec![]) } fn get_all_indexes_as_array(&mut self) -> Result, ExecError> { let num = (self.values)().len(); Ok((0..num).map(|k| k.to_string()).collect()) } fn get_as_single(&mut self) -> Result { let vs = (self.values)(); if vs.is_empty() { Ok("".to_string()) }else{ Ok(vs[0].clone()) } } fn is_array(&self) -> bool { true } fn len(&mut self) -> usize { (self.values)().len() } fn has_key(&mut self, key: &str) -> Result { if key == "@" || key == "*" { return Ok(true); } let n = self.index_of(key)?; Ok(n < (self.values)().len()) } fn index_based_len(&mut self) -> usize { self.len() } fn elem_len(&mut self, key: &str) -> Result { if key == "@" || key == "*" { return Ok(self.len()); } let n = self.index_of(key)?; let vs = (self.values)(); if n < vs.len() { return Ok(vs[n].to_string().len()); } Ok(0) } fn remove_elem(&mut self, _: &str) -> Result<(), ExecError> { Ok(()) } fn set_flag(&mut self, flag: char) { if ! self.flags.contains(flag) { self.flags.push(flag); } } fn unset_flag(&mut self, flag: char) { self.flags.retain(|e| e != flag); } fn has_flag(&mut self, flag: char) -> bool { self.flags.contains(flag) } fn get_flags(&mut self) -> &str { &self.flags } } impl OnDemandArray { pub fn new(values: fn() -> Vec) -> Self { Self { values: values, flags: "a".to_string(), } } fn index_of(&mut self, key: &str) -> Result { let index = match key.parse::() { Ok(i) => i, _ => return Err(ExecError::ArrayIndexInvalid(key.to_string())), }; Ok(index as usize) } } ================================================ FILE: src/core/database/data/assoc.rs ================================================ //SPDXFileCopyrightText: 2025 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use super::{case_change, Data}; use crate::error::exec::ExecError; use crate::utils; use std::collections::HashMap; #[derive(Debug, Clone)] pub struct AssocData { body: HashMap, last: Option, pub flags: String, } impl From> for AssocData { fn from(hm: HashMap) -> Self { Self { body: hm, last: None, flags: "A".to_string(), } } } impl Data for AssocData { fn boxed_clone(&self) -> Box { Box::new(self.clone()) } fn get_fmt_string(&mut self) -> String { self._get_fmt_string() } fn _get_fmt_string(&self) -> String { let mut formatted = String::new(); formatted += "("; for k in self.keys() { let v = &self.get(&k).unwrap_or("".to_string()); let mut ansi = utils::to_ansi_c(v); if ansi == *v { ansi = format!("\"{}\"", &ansi); } let k = utils::to_ansi_c(&k); formatted += &format!("[{}]={} ", k, &ansi); } formatted += ")"; formatted } fn clear(&mut self) { self.body.clear(); } fn set_as_single(&mut self, name: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; let mut value = value.to_string(); case_change(&self.flags, &mut value); self.body.insert("0".to_string(), value); Ok(()) } fn set_as_assoc(&mut self, name: &str, key: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; let mut value = value.to_string(); case_change(&self.flags, &mut value); self.body.insert(key.to_string(), value.clone()); self.last = Some(value); Ok(()) } fn append_to_assoc_elem(&mut self, name: &str, key: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; let mut value = if let Some(v) = self.body.get(key) { v.to_owned() + value } else { value.to_string() }; case_change(&self.flags, &mut value); self.body.insert(key.to_string(), value.clone()); self.last = Some(value); Ok(()) } fn get_as_assoc(&mut self, key: &str) -> Result { if key == "@" || key == "*" { return Ok(self.values().join(" ")); } match self.body.get(key) { Some(s) => Ok(s.to_string()), None => Err(ExecError::ArrayIndexInvalid(key.to_string())), } } fn get_as_single(&mut self) -> Result { if let Some(s) = self.body.get("0") { return Ok(s.to_string()); } self.last .clone() .ok_or(ExecError::Other("No last input".to_string())) } fn is_assoc(&self) -> bool { true } fn len(&mut self) -> usize { self.body.len() } fn has_key(&mut self, key: &str) -> Result { if key == "@" || key == "*" { return Ok(true); } Ok(self.body.contains_key(key)) } fn elem_len(&mut self, key: &str) -> Result { if key == "@" || key == "*" { return Ok(self.len()); } let s = self.body.get(key).unwrap_or(&"".to_string()).clone(); Ok(s.chars().count()) } fn get_all_indexes_as_array(&mut self) -> Result, ExecError> { Ok(self.keys().clone()) } fn get_all_as_array(&mut self, skip_none: bool) -> Result, ExecError> { if self.body.is_empty() { return Ok(vec![]); } let mut keys = self.keys(); keys.sort(); let mut ans = vec![]; for i in keys { match self.body.get(&i) { Some(s) => ans.push(s.clone()), None => { if !skip_none { ans.push("".to_string()); } } } } Ok(ans) } fn get_vec_from(&mut self, _: usize, skip_non: bool) -> Result, ExecError> { self.get_all_as_array(skip_non) } fn remove_elem(&mut self, key: &str) -> Result<(), ExecError> { if key == "*" || key == "@" { // self.body.clear(); return Ok(()); } self.body.remove(key); Ok(()) } fn set_flag(&mut self, flag: char) { if ! self.flags.contains(flag) { self.flags.push(flag); } } fn unset_flag(&mut self, flag: char) { self.flags.retain(|e| e != flag); } fn has_flag(&mut self, flag: char) -> bool { self.flags.contains(flag) } fn get_flags(&mut self) -> &str { &self.flags } } impl AssocData { pub fn new() -> Self { Self { body: HashMap::new(), last: None, flags: "A".to_string(), } } pub fn get(&self, key: &str) -> Option { self.body.get(key).cloned() } pub fn keys(&self) -> Vec { let mut keys: Vec = self.body.iter().map(|e| e.0.clone()).collect(); keys.sort(); keys } pub fn values(&self) -> Vec { self.body.iter().map(|e| e.1.clone()).collect() } } ================================================ FILE: src/core/database/data/assoc_int.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use super::assoc::AssocData; use super::Data; use crate::error::exec::ExecError; use crate::utils; use std::collections::HashMap; #[derive(Debug, Clone, Default)] pub struct IntAssocData { body: HashMap, last: Option, pub flags: String, } impl Data for IntAssocData { fn boxed_clone(&self) -> Box { Box::new(self.clone()) } fn get_fmt_string(&mut self) -> String { self._get_fmt_string() } fn _get_fmt_string(&self) -> String { let mut formatted = String::new(); formatted += "("; for k in self.keys() { let v = &self.get(&k).unwrap_or("".to_string()); let mut ansi = utils::to_ansi_c(v); if ansi == *v { ansi = format!("\"{}\"", &ansi); } let k = utils::to_ansi_c(&k); formatted += &format!("[{}]={} ", k, &ansi); } formatted += ")"; formatted } fn clear(&mut self) { self.body.clear(); } fn set_as_single(&mut self, name: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; let n = super::to_int(value)?; self.body.insert("0".to_string(), n); Ok(()) } fn set_as_assoc(&mut self, name: &str, key: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; let n = super::to_int(value)?; self.body.insert(key.to_string(), n); self.last = Some(value.to_string()); Ok(()) } fn append_to_assoc_elem(&mut self, name: &str, key: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; let n = super::to_int(value)?; if let Some(v) = self.body.get(key) { self.body.insert(key.to_string(), v + n); } else { self.body.insert(key.to_string(), n); } self.last = Some(value.to_string()); Ok(()) } fn get_as_assoc(&mut self, key: &str) -> Result { if key == "@" || key == "*" { return Ok(self.values().join(" ")); } match self.body.get(key) { Some(s) => Ok(s.to_string()), None => Err(ExecError::ArrayIndexInvalid(key.to_string())), } } fn get_as_single(&mut self) -> Result { if let Some(s) = self.body.get("0") { return Ok(s.to_string()); } self.last .clone() .ok_or(ExecError::Other("No last input".to_string())) } fn get_str_type(&self) -> Box { let mut hash = HashMap::new(); for d in &self.body { hash.insert(d.0.to_string(), d.1.to_string()); } let mut new_d = AssocData::from(hash); new_d.flags = self.flags.clone(); new_d.unset_flag('i'); Box::new(new_d) } fn is_assoc(&self) -> bool { true } fn len(&mut self) -> usize { self.body.len() } fn has_key(&mut self, key: &str) -> Result { if key == "@" || key == "*" { return Ok(true); } Ok(self.body.contains_key(key)) } fn elem_len(&mut self, key: &str) -> Result { if key == "@" || key == "*" { return Ok(self.len()); } let s = *self.body.get(key).unwrap_or(&0); Ok(s.to_string().len()) } fn get_all_indexes_as_array(&mut self) -> Result, ExecError> { Ok(self.keys().clone()) } fn get_all_as_array(&mut self, skip_none: bool) -> Result, ExecError> { if self.body.is_empty() { return Ok(vec![]); } let mut keys = self.keys(); keys.sort(); let mut ans = vec![]; for i in keys { match self.body.get(&i) { Some(s) => ans.push(s.to_string()), None => { if !skip_none { ans.push("".to_string()); } } } } Ok(ans) } fn get_vec_from(&mut self, _: usize, skip_non: bool) -> Result, ExecError> { self.get_all_as_array(skip_non) } fn remove_elem(&mut self, key: &str) -> Result<(), ExecError> { if key == "*" || key == "@" { // self.body.clear(); return Ok(()); } self.body.remove(key); Ok(()) } fn set_flag(&mut self, flag: char) { if ! self.flags.contains(flag) { self.flags.push(flag); } } fn unset_flag(&mut self, flag: char) { self.flags.retain(|e| e != flag); } fn has_flag(&mut self, flag: char) -> bool { if flag == 'i' { return true; } self.flags.contains(flag) } fn get_flags(&mut self) -> &str { &self.flags } } impl IntAssocData { pub fn new() -> Self { Self { body: HashMap::new(), last: None, flags: "i".to_string() } } pub fn get(&self, key: &str) -> Option { Some(self.body.get(key).unwrap_or(&0).to_string()) } pub fn keys(&self) -> Vec { let mut keys: Vec = self.body.iter().map(|e| e.0.clone()).collect(); keys.sort(); keys //self.body.iter().map(|e| e.0.clone()).collect() } pub fn values(&self) -> Vec { self.body.iter().map(|e| e.1.to_string()).collect() } } ================================================ FILE: src/core/database/data/random.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use super::Data; use crate::error::exec::ExecError; use rand_chacha::rand_core::RngCore; use rand_chacha::rand_core::SeedableRng; use rand_chacha::ChaCha20Rng; #[derive(Debug, Clone)] pub struct RandomVar { rng: ChaCha20Rng, flags: String, } impl Data for RandomVar { fn boxed_clone(&self) -> Box { Box::new(self.clone()) } fn _get_fmt_string(&self) -> String { "".to_string() } fn get_fmt_string(&mut self) -> String { self.get_as_single().unwrap() } fn get_as_single(&mut self) -> Result { let rand = self.rng.next_u32() & 0x7FFF; Ok(rand.to_string()) } fn len(&mut self) -> usize { self.get_as_single().unwrap().len() } fn set_as_single(&mut self, name: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; let seed = value.parse::().unwrap_or(0); self.rng = ChaCha20Rng::seed_from_u64(seed + 4011); //4011: for bash test Ok(()) } fn is_special(&self) -> bool { true } fn set_flag(&mut self, flag: char) { if ! self.flags.contains(flag) { self.flags.push(flag); } } fn unset_flag(&mut self, flag: char) { self.flags.retain(|e| e != flag); } fn get_flags(&mut self) -> &str { &self.flags } } impl RandomVar { pub fn new() -> Self { Self { rng: ChaCha20Rng::seed_from_u64(0), flags: "i".to_string(), } } } ================================================ FILE: src/core/database/data/seconds.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use super::Data; use crate::error::exec::ExecError; use crate::utils::clock; use std::time::Duration; #[derive(Debug, Clone)] pub struct Seconds { origin: Duration, shift: isize, flags: String, } impl Data for Seconds { fn boxed_clone(&self) -> Box { Box::new(self.clone()) } fn _get_fmt_string(&self) -> String { "".to_string() //TODO } fn get_as_single(&mut self) -> Result { let elapsed = clock::monotonic_time() - self.origin; let ans = format!("{}", elapsed.as_secs() as isize + self.shift); Ok(ans) } fn len(&mut self) -> usize { 0 } fn set_as_single(&mut self, name: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; self.shift = value.parse::().unwrap_or(0); self.origin = clock::monotonic_time(); Ok(()) } fn is_special(&self) -> bool { true } fn is_single_num(&self) -> bool { true } fn set_flag(&mut self, flag: char) { if ! self.flags.contains(flag) { self.flags.push(flag); } } fn unset_flag(&mut self, flag: char) { self.flags.retain(|e| e != flag); } fn get_flags(&mut self) -> &str { &self.flags } } impl Seconds { pub fn new() -> Self { Self { origin: clock::monotonic_time(), shift: 0, flags: "i".to_string(), } } } ================================================ FILE: src/core/database/data/single.rs ================================================ //SPDXFileCopyrightText: 2025 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use super::{case_change, Data}; use crate::error::exec::ExecError; use crate::utils; #[derive(Debug, Clone)] pub struct SingleData { pub body: String, pub flags: String, } impl From<&str> for SingleData { fn from(s: &str) -> Self { Self { body: s.to_string(), flags: String::new(), } } } impl Data for SingleData { fn boxed_clone(&self) -> Box { Box::new(self.clone()) } fn get_fmt_string(&mut self) -> String { self._get_fmt_string() } fn _get_fmt_string(&self) -> String { let mut s = self.body.replace("'", "\\'"); if s.contains('~') || s.starts_with('#') { s = "'".to_owned() + &s + "'"; } let ansi = utils::to_ansi_c(&s); if ansi == s { ansi.replace("$", "\\$") } else { ansi } } fn clear(&mut self) { self.body.clear(); } fn set_as_single(&mut self, name: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; self.nameref_check(name, value)?; /* if self.has_flag('n') { if value.contains('[') { let splits: Vec<&str> = value.split('[').collect(); if ! utils::is_var(&splits[0]) || ! splits[1].ends_with(']') { return Err(ExecError::InvalidNameRef(value.to_string())); } if name == splits[0] { return Err(ExecError::SelfRef(name.to_string())); } }else if value == "" { }else if ! utils::is_var(value) { return Err(ExecError::InvalidNameRef(value.to_string())); }else if name == value { return Err(ExecError::SelfRef(name.to_string())); } }*/ self.body = value.to_string(); case_change(&self.flags, &mut self.body); Ok(()) } fn append_as_single(&mut self, name: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; self.body += value; case_change(&self.flags, &mut self.body); Ok(()) } fn get_as_single(&mut self) -> Result { Ok(self.body.to_string()) } fn len(&mut self) -> usize { self.body.chars().count() } fn is_single(&self) -> bool { true } fn has_key(&mut self, key: &str) -> Result { if key == "@" || key == "*" { return Ok(true); } Ok(key == "0") } fn set_flag(&mut self, flag: char) { if ! self.flags.contains(flag) { self.flags.push(flag); } } fn unset_flag(&mut self, flag: char) { self.flags.retain(|e| e != flag); } fn get_flags(&mut self) -> &str { &self.flags } } impl SingleData { pub fn new(flags: &str) -> Self { Self { body: "".to_string(), flags: flags.to_string(), } } } ================================================ FILE: src/core/database/data/single_int.rs ================================================ //SPDXFileCopyrightText: 2025 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use super::single::SingleData; use super::Data; use crate::error::exec::ExecError; use crate::utils; #[derive(Debug, Clone)] pub struct IntData { pub body: isize, pub flags: String, } impl Data for IntData { fn boxed_clone(&self) -> Box { Box::new(self.clone()) } fn _get_fmt_string(&self) -> String { utils::to_ansi_c(&self.body.to_string()) } fn clear(&mut self) {} fn set_as_single(&mut self, name: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; match value.parse::() { Ok(n) => self.body = n, Err(e) => { return Err(ExecError::Other(e.to_string())); } } Ok(()) } fn append_as_single(&mut self, name: &str, value: &str) -> Result<(), ExecError> { self.readonly_check(name)?; match value.parse::() { Ok(n) => self.body += n, Err(e) => { return Err(ExecError::Other(e.to_string())); } } Ok(()) } fn init_as_num(&mut self) -> Result<(), ExecError> { self.body = 0; Ok(()) } fn get_as_single(&mut self) -> Result { Ok(self.body.to_string()) } fn get_as_single_num(&mut self) -> Result { Ok(self.body) } fn get_str_type(&self) -> Box { let mut d = SingleData::from(self.body.to_string().as_ref()); d.flags = self.flags.clone(); let _ = d.unset_flag('i'); Box::new(d) } fn len(&mut self) -> usize { self.body.to_string().len() } fn is_single(&self) -> bool { true } fn is_single_num(&self) -> bool { true } fn has_key(&mut self, key: &str) -> Result { if key == "@" || key == "*" { return Ok(true); } Ok(key == "0") } fn set_flag(&mut self, flag: char) { if ! self.flags.contains(flag) { self.flags.push(flag); } } fn unset_flag(&mut self, flag: char) { self.flags.retain(|e| e != flag); } fn has_flag(&mut self, flag: char) -> bool { if flag == 'i' { return true; } self.flags.contains(flag) } fn get_flags(&mut self) -> &str { &self.flags } } impl IntData { pub fn new() -> Self { Self { body: 0, flags: "i".to_string(), } } } ================================================ FILE: src/core/database/data/single_ondemand.rs ================================================ //SPDXFileCopyrightText: 2025 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use super::{Data, ExecError}; #[derive(Debug, Clone)] pub struct OnDemandSingle { value: fn() -> String, flags: String, } impl Data for OnDemandSingle { fn boxed_clone(&self) -> Box { Box::new(self.clone()) } fn get_fmt_string(&mut self) -> String { self.get_as_single().unwrap() } fn get_as_single(&mut self) -> Result { Ok((self.value)()) } fn set_as_single(&mut self, name: &str, _: &str) -> Result<(), ExecError> { self.readonly_check(name) } fn len(&mut self) -> usize { (self.value)().chars().count() } fn set_flag(&mut self, flag: char) { if ! self.flags.contains(flag) { self.flags.push(flag); } } fn get_flags(&mut self) -> &str { &self.flags } } impl OnDemandSingle { pub fn new(timefn: fn() -> String) -> Self { Self { value: timefn, flags: "".to_string(), } } } ================================================ FILE: src/core/database/data/srandom.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use super::Data; use crate::error::exec::ExecError; use rand_chacha::rand_core::{RngCore, SeedableRng}; use rand_chacha::ChaCha20Rng; #[derive(Debug, Clone)] pub struct SRandomVar { rng: ChaCha20Rng, prev: String, flags: String, } impl Data for SRandomVar { fn boxed_clone(&self) -> Box { Box::new(self.clone()) } fn _get_fmt_string(&self) -> String { self.prev.clone() } fn get_as_single(&mut self) -> Result { let rand = self.rng.next_u32(); self.prev = rand.to_string(); Ok(self.prev.clone()) } fn len(&mut self) -> usize { self.prev.len() } fn is_special(&self) -> bool { true } fn get_flags(&mut self) -> &str { &self.flags } } impl SRandomVar { pub fn new() -> Self { Self { rng: ChaCha20Rng::from_os_rng(), prev: "".to_string(), flags: "i".to_string(), } } } ================================================ FILE: src/core/database/data/uninit.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use super::Data; use crate::error::exec::ExecError; use super::super::{ ArrayData, AssocData, IntArrayData, IntAssocData, IntData, SingleData }; #[derive(Debug, Clone)] pub struct Uninit { flags: String, } impl Data for Uninit { fn boxed_clone(&self) -> Box { Box::new(self.clone()) } fn _get_fmt_string(&self) -> String { "".to_string() } fn initialize(&mut self) -> Option> { let num = self.has_flag('i'); if self.has_flag('a') { match num { true => { let mut d = IntArrayData::new(); d.flags = self.flags.clone(); return Some(Box::new(d)); }, false => { let mut d = ArrayData::new(); d.flags = self.flags.clone(); return Some(Box::new(d)); }, } } if self.has_flag('A') { match num { true => { let mut d = IntAssocData::new(); d.flags = self.flags.clone(); return Some(Box::new(d)); }, false => { let mut d = AssocData::new(); d.flags = self.flags.clone(); return Some(Box::new(d)); }, } } match num { true => { let mut d = IntData::new(); d.flags = self.flags.clone(); Some(Box::new(d)) }, false => { let d = SingleData::new(&self.flags); Some(Box::new(d)) }, } } fn clear(&mut self) {} fn is_initialized(&self) -> bool { false } fn get_as_array(&mut self, _: &str, _: &str) -> Result { Ok("".to_string()) } fn get_all_as_array(&mut self, _: bool) -> Result, ExecError> { Ok(vec![]) } fn get_all_indexes_as_array(&mut self) -> Result, ExecError> { Ok(vec![]) } fn get_as_single(&mut self) -> Result { Ok("".to_string()) } fn len(&mut self) -> usize { 0 } fn elem_len(&mut self, _: &str) -> Result { Ok(0) } fn remove_elem(&mut self, _: &str) -> Result<(), ExecError> { Ok(()) } fn set_flag(&mut self, flag: char) { if ! self.flags.contains(flag) { self.flags.push(flag); } } fn unset_flag(&mut self, flag: char) { self.flags.retain(|e| e != flag); } fn is_assoc(&self) -> bool { self.flags.contains('A') } fn is_array(&self) -> bool { self.flags.contains('a') } fn get_flags(&mut self) -> &str { &self.flags } } impl Uninit { pub fn new(flags: &str) -> Self { Self { flags: flags.to_string() } } } ================================================ FILE: src/core/database/data.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause pub mod array; pub mod array_int; pub mod assoc; pub mod assoc_int; pub mod array_ondemand; pub mod random; pub mod seconds; pub mod single; pub mod single_int; pub mod single_ondemand; pub mod srandom; pub mod uninit; use crate::error::arith::ArithError; use crate::error::exec::ExecError; use std::fmt; use std::fmt::Debug; use crate::utils; fn to_int(s: &str) -> Result { match s.parse::() { Ok(n) => Ok(n), Err(e) => Err(ArithError::OperandExpected(e.to_string()).into()), } } fn case_change(flags: &str, text: &mut String) { if flags.contains('l') { *text = text.to_lowercase(); }else if flags.contains('u') { *text = text.to_uppercase(); } } impl Debug for dyn Data { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct(&self._get_fmt_string()).finish() } } impl Clone for Box { fn clone(&self) -> Box { self.boxed_clone() } } pub trait Data { fn boxed_clone(&self) -> Box; fn _get_fmt_string(&self) -> String { "********".to_string() } fn get_fmt_string(&mut self) -> String { self._get_fmt_string() } fn get_str_type(&self) -> Box { self.boxed_clone() } fn is_initialized(&self) -> bool { true } fn initialize(&mut self) -> Option> { None } fn has_key(&mut self, _: &str) -> Result { Ok(false) } fn clear(&mut self) {} fn set_as_single(&mut self, name: &str, _: &str) -> Result<(), ExecError> { self.readonly_check(name) } fn append_as_single(&mut self, name: &str, _: &str) -> Result<(), ExecError> { self.readonly_check(name) } fn get_as_single_num(&mut self) -> Result { Err(ExecError::Other("not a single variable".to_string())) } fn set_as_array(&mut self, name: &str, _: &str, _: &str) -> Result<(), ExecError> { self.readonly_check(name)?; Err(ExecError::Other("not an array".to_string())) } fn append_to_array_elem(&mut self, name: &str, _: &str, _: &str) -> Result<(), ExecError> { self.readonly_check(name)?; Err(ExecError::Other("not an array".to_string())) } fn set_as_assoc(&mut self, name: &str, _: &str, _: &str) -> Result<(), ExecError> { self.readonly_check(name)?; Err(ExecError::Other("not an associative table".to_string())) } fn append_to_assoc_elem(&mut self, name: &str, _: &str, _: &str) -> Result<(), ExecError> { self.readonly_check(name)?; Err(ExecError::Other("not an associative table".to_string())) } fn get_as_single(&mut self) -> Result { Err(ExecError::Other("not a single variable".to_string())) } fn get_as_array(&mut self, key: &str, _: &str) -> Result { //TODO: change to ArithError let err = ArithError::OperandExpected(key.to_string()); Err(ExecError::ArithError(key.to_string(), err)) } fn get_as_assoc(&mut self, key: &str) -> Result { let err = ArithError::OperandExpected(key.to_string()); Err(ExecError::ArithError(key.to_string(), err)) } fn get_as_array_or_assoc(&mut self, pos: &str, ifs: &str) -> Result { if self.is_assoc() { return match self.get_as_assoc(pos) { Ok(d) => Ok(d), Err(_) => Ok("".to_string()), }; } if self.is_array() { return match self.get_as_array(pos, ifs) { Ok(d) => Ok(d), Err(_) => Ok("".to_string()), }; } if pos == "0" || pos == "*" || pos == "@" { match self.get_as_single() { Ok(d) => Ok(d), Err(_) => Ok("".to_string()), } } else { Ok("".to_string()) } } fn get_all_as_array(&mut self, flatten: bool) -> Result, ExecError> { self.get_vec_from(0, flatten) } fn get_vec_from(&mut self, _: usize, _: bool) -> Result, ExecError> { Err(ExecError::Other("not an array".to_string())) } fn get_all_indexes_as_array(&mut self) -> Result, ExecError> { Err(ExecError::Other("not an array".to_string())) } fn is_special(&self) -> bool { false } fn is_single(&self) -> bool { false } fn is_single_num(&self) -> bool { false } fn is_assoc(&self) -> bool { false } fn is_array(&self) -> bool { false } fn len(&mut self) -> usize; fn index_based_len(&mut self) -> usize { self.len() } fn elem_len(&mut self, key: &str) -> Result { match key { "0" => Ok(self.len()), _ => Ok(0), } } fn init_as_num(&mut self) -> Result<(), ExecError> { Err(ExecError::Other("Undefined call init_as_num".to_string())) } fn remove_elem(&mut self, _: &str) -> Result<(), ExecError> { Err(ExecError::Other("Undefined call remove_elem".to_string())) } fn readonly_check(&mut self, name: &str) -> Result<(), ExecError> { if self.has_flag('r') { return Err(ExecError::VariableReadOnly(name.to_string())); } Ok(()) } fn set_flag(&mut self, _: char) {} fn unset_flag(&mut self, _: char) {} fn has_flag(&mut self, flag: char) -> bool { self.get_flags().contains(flag) } fn get_flags(&mut self) -> &str; fn nameref_check(&mut self, name: &str, value: &str) -> Result<(), ExecError> { if ! self.has_flag('n') { return Ok(()); } if value.contains('[') { let splits: Vec<&str> = value.split('[').collect(); if ! utils::is_var(&splits[0]) || ! splits[1].ends_with(']') { return Err(ExecError::InvalidNameRef(value.to_string())); } if name == splits[0] { return Err(ExecError::SelfRef(name.to_string())); } }else if value == "" { }else if ! utils::is_var(value) { return Err(ExecError::InvalidNameRef(value.to_string())); }else if name == value { return Err(ExecError::SelfRef(name.to_string())); } Ok(()) } } ================================================ FILE: src/core/database/database_appenders.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use std::env; use super::SingleData; use crate::core::DataBase; use crate::error::exec::ExecError; impl DataBase { pub fn append_param( &mut self, name: &str, val: &str, scope: Option, ) -> Result<(), ExecError> { self.check_on_write(name, &Some(vec![val.to_string()]))?; /* if let Some(nameref) = self.get_nameref(name)? { return self.set_param(&nameref, val, scope); }*/ if name == "BASH_ARGV0" { let n = scope.unwrap_or(self.get_scope_num() - 1); self.position_parameters[n][0] += val; } if !self.flags.contains('r') && (self.flags.contains('a') || self.has_flag(name, 'x')) && env::var(name).is_err() { unsafe{env::set_var(name, "")}; } let scope = self.get_target_scope(name, scope); if self.params[scope].get(name).is_none() { self.set_entry(scope, name, Box::new(SingleData::from("")))?; } let d = self.params[scope].get_mut(name).unwrap(); if d.is_array() { return d.append_to_array_elem(name, "0", val); } d.append_as_single(name, val)?; if env::var(name).is_ok() { let v = d.get_as_single()?; unsafe{env::set_var(name, v)}; } Ok(()) } pub fn append_param2( &mut self, name: &str, index: &str, val: &str, scope: Option, ) -> Result<(), ExecError> { if index.is_empty() { return self.append_param(name, val, scope); } if self.is_array(name) { if let Ok(n) = index.parse::() { self.set_array_elem(name, val, n, scope, true)?; } } else if self.is_assoc(name) { self.append_to_assoc_elem(name, index, val, scope)?; } else { match index.parse::() { Ok(n) => self.set_array_elem(name, val, n, scope, true)?, _ => self.append_to_assoc_elem(name, index, val, scope)?, } } Ok(()) } pub fn append_to_assoc_elem(&mut self, name: &str, key: &str, val: &str, scope: Option, ) -> Result<(), ExecError> { self.check_on_write(name, &Some(vec![val.to_string()]))?; let scope = self.get_target_scope(name, scope); match self.params[scope].get_mut(name) { Some(v) => v.append_to_assoc_elem(name, key, val), _ => Err(ExecError::Other("TODO".to_string())), } } } ================================================ FILE: src/core/database/database_checkers.rs ================================================ //SPDXFileCopyrightText: 2025 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use crate::core::DataBase; use crate::error::exec::ExecError; use crate::utils; use crate::utils::restricted_shell; impl DataBase { pub(super) fn check_on_write(&mut self, name: &str, values: &Option> ) -> Result<(), ExecError> { Self::name_check(name)?; restricted_shell::check(self, name, values)?; Ok(()) } pub fn has_array_value(&mut self, name: &str, index: &str) -> bool { match self.get_ref(name) { Some(d) => d.has_key(index).unwrap_or(false), None => false, } } pub fn has_flag_scope(&mut self, name: &str, flag: char, scope: usize) -> bool { if let Some(e) = self.params[scope].get_mut(name) { return e.has_flag(flag); } false } pub fn has_flag(&mut self, name: &str, flag: char) -> bool { match self.get_ref(name) { Some(d) => d.has_flag(flag), None => false, } } pub fn exist(&mut self, name: &str) -> bool { if let Ok(Some(nameref)) = self.get_nameref(name) { return self.exist(&nameref); } if let Ok(n) = name.parse::() { let scope = self.position_parameters.len() - 1; return n < self.position_parameters[scope].len(); } let num = self.params.len(); for scope in (0..num).rev() { if self.params[scope].contains_key(name) { return true; } } false } pub fn exist_nameref(&mut self, name: &str) -> bool { if let Some(d) = self.get_ref(name) { return d.has_flag('n'); } false } pub fn exist_l(&mut self, name: &str, scope: usize) -> bool { if scope >= self.params.len() { return false; } self.params[scope].contains_key(name) } pub fn exist_nameref_l(&mut self, name: &str, scope: usize) -> bool { if let Some(d) = self.params[scope].get_mut(name) { return d.has_flag('n'); } false } pub fn has_key(&mut self, name: &str, key: &str) -> Result { let num = self.params.len(); for scope in (0..num).rev() { if let Some(e) = self.params[scope].get_mut(name) { return e.has_key(key); } } Ok(false) } pub fn name_check(name: &str) -> Result<(), ExecError> { if !utils::is_param(name) { return Err(ExecError::VariableInvalid(name.to_string())); } Ok(()) } pub fn is_readonly(&mut self, name: &str) -> bool { self.has_flag(name, 'r') } pub fn is_int(&mut self, name: &str) -> bool { self.has_flag(name, 'i') } pub fn is_assoc(&mut self, name: &str) -> bool { match self.get_ref(name) { Some(d) => d.is_assoc(), None => false, } } pub fn is_single(&mut self, name: &str) -> bool { match self.get_ref(name) { Some(d) => d.is_single(), _ => false, } } pub fn is_single_num(&mut self, name: &str) -> bool { match self.get_ref(name) { Some(d) => d.is_single_num(), _ => false, } } pub fn is_array(&mut self, name: &str) -> bool { match self.get_ref(name) { Some(d) => d.is_array(), _ => false, } } } ================================================ FILE: src/core/database/database_getters.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use super::data::Data; use super::DataBase; use crate::error::exec::ExecError; use std::collections::HashSet; use std::env; impl DataBase { pub fn get_ref(&mut self, name: &str) -> Option<&mut Box> { let scope = self.get_scope_pos(name)?; self.params[scope].get_mut(name) } pub fn get_ifs_head(&mut self) -> String { let ifs = self.get_param("IFS").unwrap_or(" ".to_string()); match ifs.as_str() { "" => "".to_string(), s => s.chars().next().unwrap().to_string(), } } pub fn get_scope_num(&mut self) -> usize { self.params.len() } pub fn get_param_keys(&mut self) -> Vec { let mut keys = HashSet::new(); for scope in &self.params { scope.keys() .for_each(|k| { keys.insert(k); }); } let mut ans = keys.iter() .map(|c| c.to_string()) .collect::>(); ans.sort(); ans } pub fn get_func_keys(&mut self) -> Vec { let mut keys = self.functions .keys() .map(|c| c.to_string()) .collect::>(); keys.sort(); keys } pub fn get_scope_pos(&mut self, name: &str) -> Option { let num = self.params.len(); (0..num) .rev() .find(|&scope| self.params[scope].contains_key(name)) } pub fn get_position_params(&self) -> Vec { match self.position_parameters.last() { Some(v) => v[1..].to_vec(), _ => vec![], } } pub fn get_indexes_all(&mut self, name: &str) -> Vec { let scope = self.position_parameters.len() - 1; if name == "@" { return self.position_parameters[scope].clone(); } match self.get_ref(name) { Some(d) => d.get_all_indexes_as_array().unwrap_or_default(), None => vec![], } } pub fn get_vec_from( &mut self, name: &str, pos: usize, flatten: bool, ) -> Result, ExecError> { let scope = self.position_parameters.len() - 1; if name == "@" { return Ok(self.position_parameters[scope].clone()); } match self.get_ref(name) { Some(d) => { if let Ok(v) = d.get_vec_from(pos, flatten) { return Ok(v); } Ok(vec![]) } None => { if self.flags.contains('u') { return Err(ExecError::UnboundVariable(name.to_string())); } Ok(vec![]) } } } pub fn get_var_len(&mut self, name: &str) -> usize { if let Some(d) = self.get_ref(name) { return d.len(); } 0 } pub fn index_based_len(&mut self, name: &str) -> usize { if let Some(d) = self.get_ref(name) { return d.index_based_len(); } 0 } pub fn get_vec(&mut self, name: &str, flatten: bool) -> Result, ExecError> { self.get_vec_from(name, 0, flatten) } pub fn get_elem(&mut self, name: &str, pos: &str) -> Result { Self::name_check(name)?; let scope = self.get_scope_pos(name); if scope.is_none() { return match self.flags.contains('u') { true => Err(ExecError::UnboundVariable(name.to_string())), false => Ok("".to_string()), }; } let ifs = self.get_ifs_head(); self.params[scope.unwrap()] .get_mut(name) .unwrap() .get_as_array_or_assoc(pos, &ifs) } pub fn get_elem_or_param(&mut self, name: &str, index: &str) -> Result { match index.is_empty() { true => self.get_param(name), false => self.get_elem(name, index), } } pub fn get_elem_len(&mut self, name: &str, key: &str) -> Result { Self::name_check(name)?; if let Some(v) = self.get_ref(name) { if key == "@" || key == "*" { if ! v.is_initialized() { return Ok(0); } if ! v.is_array() && ! v.is_assoc() { return Ok(1); } } return v.elem_len(key); } if self.flags.contains('u') { return Err(ExecError::UnboundVariable(name.to_string())); } Ok(0) } pub fn get_braced_param_hash_length(&mut self, name: &str) -> Result { Self::name_check(name)?; if name == "@" || name == "*" { let scope = self.position_parameters.len(); return Ok(self.position_parameters[scope - 1].len() - 1); } if let Ok(n) = name.parse::() { return Ok(position_param(self, n)?.chars().count()); } if let Some(v) = self.get_ref(name) { return v.elem_len("0"); } if let Ok(v) = env::var(name) { return Ok(v.chars().count()); } if self.flags.contains('u') { return Err(ExecError::UnboundVariable(name.to_string())); } Ok(0) } pub fn get_param(&mut self, name: &str) -> Result { Self::name_check(name)?; if let Some(nameref) = self.get_nameref(name)? { return self.get_param(&nameref); } if let Some(val) = special_param(self, name) { return Ok(val); } if name == "@" || name == "*" { return connected_position_params(self, name == "*"); } //in double quoted subword, this method should not be used if let Ok(n) = name.parse::() { return position_param(self, n); } if let Some(v) = self.get_ref(name) { if v.is_special() { return v.get_as_single(); } else if v.is_single_num() { let val = v.get_as_single_num()?; //.unwrap_or_default(); return Ok(val.to_string()); } else { let val = v.get_as_single().unwrap_or_default(); return Ok(val); } } if let Ok(v) = env::var(name) { let _ = self.set_param(name, &v, Some(0)); return Ok(v); } if self.flags.contains('u') { return Err(ExecError::UnboundVariable(name.to_string())); } Ok("".to_string()) } pub fn get_nameref(&mut self, name: &str) -> Result, ExecError> { let d = match self.get_ref(name) { Some(d) => d, None => return Ok(None), }; if ! d.has_flag('n') { return Ok(None); } if ! d.is_initialized() { return Ok(None); } match d.get_as_single() { Ok(nameref) => Ok(Some(nameref)), Err(e) => Err(e), } } pub fn get_flags(&mut self, name: &str) -> &str { if let Some(v) = self.get_ref(name) { v.get_flags() }else{ "" } } } fn special_param(db: &DataBase, name: &str) -> Option { let val = match name { "-" => db.flags.clone(), "?" => db.exit_status.to_string(), "_" => db.last_arg.clone(), "#" => { let pos = db.position_parameters.len() - 1; (db.position_parameters[pos].len() - 1).to_string() } _ => return None, }; Some(val) } fn connected_position_params(db: &mut DataBase, aster: bool) -> Result { let mut joint = " ".to_string(); if aster { joint = db.get_ifs_head(); } match db.position_parameters.last() { Some(a) => Ok(a[1..].join(&joint)), _ => Ok("".to_string()), } } fn position_param(db: &DataBase, pos: usize) -> Result { let scope = db.position_parameters.len(); match db.position_parameters[scope - 1].len() > pos { true => Ok(db.position_parameters[scope - 1][pos].to_string()), false => Ok(String::new()), } } ================================================ FILE: src/core/database/database_initializers.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use crate::core::DataBase; use crate::core::database::{Data, IntData, Uninit, AssocData, IntAssocData, ArrayData, IntArrayData, OnDemandArray}; use crate::error::exec::ExecError; use crate::utils; use crate::utils::clock; use super::data::single_ondemand::OnDemandSingle; use super::data::random::RandomVar; use super::data::seconds::Seconds; use super::data::srandom::SRandomVar; use std::{env, process}; use nix::unistd; impl DataBase { pub(super) fn initialize(&mut self) -> Result<(), String> { self.exit_status = 0; self.set_param("$", &process::id().to_string(), None)?; //self.set_param("BASHPID", &process::id().to_string(), None)?; let bashpid = || process::id().to_string(); self.params[0].insert("BASHPID".to_string(), Box::new(OnDemandSingle::new(bashpid))); self.set_flag("BASHPID", 'i', 0); self.set_param("BASH_SUBSHELL", "0", None)?; self.set_param("HOME", &env::var("HOME").unwrap_or("/".to_string()), None)?; self.set_param("OPTIND", "1", None)?; self.set_param("IFS", " \t\n", None)?; self.init_as_num("UID", &unistd::getuid().to_string(), None)?; self.set_flag("UID", 'i', 0); self.set_flag("UID", 'r', 0); self.params[0].insert("RANDOM".to_string(), Box::new(RandomVar::new())); self.params[0].insert("SRANDOM".to_string(), Box::new(SRandomVar::new())); self.params[0].insert("SECONDS".to_string(), Box::new(Seconds::new())); self.params[0].insert("EPOCHSECONDS".to_string(), Box::new(OnDemandSingle::new(clock::get_epochseconds))); self.params[0].insert("EPOCHREALTIME".to_string(), Box::new(OnDemandSingle::new(clock::get_epochrealtime))); self.params[0].insert("GROUPS".to_string(), Box::new(OnDemandArray::new(utils::groups))); self.init_array("BASH_SOURCE", Some(vec![]), None, false)?; self.init_array("BASH_ARGC", Some(vec![]), None, false)?; self.init_array("BASH_ARGV", Some(vec![]), None, false)?; self.init_array("BASH_LINENO", Some(vec![]), None, false)?; self.init_array("DIRSTACK", Some(vec![]), None, false)?; self.init_assoc("BASH_ALIASES", None, true, false)?; self.init_assoc("BASH_CMDS", None, true, false)?; Ok(()) } pub fn init_as_num(&mut self, name: &str, value: &str, scope: Option, ) -> Result<(), ExecError> { self.check_on_write(name, &Some(vec![]))?; let mut data = IntData::new(); if !value.is_empty() { match value.parse::() { Ok(n) => data.body = n, Err(e) => return Err(ExecError::Other(e.to_string())), } } let scope = self.get_target_scope(name, scope); self.set_entry(scope, name, Box::new(data)) } pub fn init_array( &mut self, name: &str, v: Option>, scope: Option, i_flag: bool, ) -> Result<(), ExecError> { self.check_on_write(name, &v)?; let scope = self.get_target_scope(name, scope); if i_flag { let mut obj = IntArrayData::new(); if let Some(v) = v { for (i, e) in v.into_iter().enumerate() { obj.set_as_array(name, &i.to_string(), &e)?; } } return self.set_entry(scope, name, Box::new(obj)); } if v.is_none() { return self.set_entry(scope, name, Box::new(Uninit::new("a"))); } let mut obj = ArrayData::new(); for (i, e) in v.unwrap().into_iter().enumerate() { obj.set_as_array(name, &i.to_string(), &e)?; } self.set_entry(scope, name, Box::new(obj)) } pub fn init_assoc( &mut self, name: &str, scope: Option, set_array: bool, i_flag: bool, ) -> Result<(), ExecError> { self.check_on_write(name, &None)?; let obj = if i_flag { Box::new(IntAssocData::new()) as Box:: } else if set_array { Box::new(AssocData::new()) as Box:: } else { Box::new(Uninit::new("A")) as Box:: }; let scope = self.get_target_scope(name, scope); self.set_entry(scope, name, obj) } } ================================================ FILE: src/core/database/database_print.rs ================================================ //SPDXFileCopyrightText: 2025 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use super::{Data, DataBase}; impl DataBase { pub fn print_params_and_funcs(&mut self) { self.get_param_keys() .into_iter() .for_each(|k| self.print_param(&k)); self.get_func_keys() .into_iter() .for_each(|k| { self.print_func(&k); }); } pub fn print_param(&mut self, name: &str) { if let Some(d) = self.get_ref(name) { Self::print_with_name(d, name, false); } } pub fn print_func(&mut self, name: &str) -> bool { if let Some(f) = self.functions.get_mut(name) { f.pretty_print(0); return true; } false } pub fn print_for_declare(&mut self, name: &str) { if let Some(d) = self.get_ref(name) { Self::print_with_name(d, name, true); } } fn print_with_name(d: &mut Box::, name: &str, declare_print: bool) { let body = d.get_fmt_string(); if !d.is_initialized() { println!("{name}"); } else if declare_print && (d.is_single() || d.is_special() ) && !body.starts_with("\"") && !body.ends_with("\"") { println!("{name}=\"{body}\""); } else { println!("{name}={body}"); } } } ================================================ FILE: src/core/database/database_setters/database_setter_backend.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use std::env; use super::{ArrayData, Data, Uninit}; use crate::core::DataBase; use crate::error::exec::ExecError; impl DataBase { pub(super) fn set_elem(&mut self, scope: usize, name: &str, pos: isize, val: &String, i_flag: bool ) -> Result<(), ExecError> { if self.is_readonly(name) { return Err(ExecError::VariableReadOnly(name.to_string())); } if ! self.params[scope].contains_key(name) { self.set_uninit_array(scope, name, i_flag)?; return self.set_elem(scope, name, pos, val, i_flag); } let d = self.params[scope].get_mut(name).unwrap(); if let Some(init_d) = d.initialize() { *d = init_d; } if d.is_array() { d.set_as_array(name, &pos.to_string(), val) } else if d.is_assoc() { d.set_as_assoc(name, &pos.to_string(), val) } else { let data = d.get_as_single()?; self.set_uninit_array(scope, name, i_flag)?; if !data.is_empty() { self.set_elem(scope, name, 0, &data, i_flag)?; } self.set_elem(scope, name, pos, val, i_flag) } } pub(super) fn append_elem(&mut self, scope: usize, name: &str, pos: isize, val: &String, ) -> Result<(), ExecError> { if ! self.params[scope].contains_key(name) { self.set_uninit_array(scope, name, false)?; return self.set_elem(scope, name, pos, val, false); } let d = self.params[scope].get_mut(name).unwrap(); if let Some(init_d) = d.initialize() { *d = init_d; } if d.is_array() { d.append_to_array_elem(name, &pos.to_string(), val) } else if d.is_assoc() { d.append_to_assoc_elem(name, &pos.to_string(), val) } else { let data = d.get_as_single()?; self.set_uninit_array(scope, name, false)?; self.append_elem(scope, name, 0, &data)?; self.append_elem(scope, name, pos, val) } } pub(super) fn set_uninit_array(&mut self, scope: usize, name: &str, i_flag: bool, ) -> Result<(), ExecError> { let obj = if i_flag { Box::new(Uninit::new("ai")) as Box:: }else { Box::new(ArrayData::from(Some(vec![]))) as Box:: }; self.set_entry(scope, name, obj) } pub fn set_entry(&mut self, scope: usize, name: &str, data: Box::) -> Result<(), ExecError> { if self.has_flag(name, 'r') { return Err(ExecError::VariableReadOnly(name.to_string())); } self.params[scope].insert(name.to_string(), data); Ok(()) } pub fn remove_entry(&mut self, scope: usize, name: &str) -> Result { if self.has_flag(name, 'r') { return Err(ExecError::VariableReadOnly(name.to_string())); } if self.params[scope].contains_key(name) { self.params[scope].remove(name); if scope == 0 { unsafe{env::remove_var(name)}; } return Ok(true); } Ok(false) } } ================================================ FILE: src/core/database/database_setters.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause mod database_setter_backend; use std::env; use super::{ArrayData, Data, IntData, SingleData, Uninit}; use crate::core::DataBase; use crate::error::exec::ExecError; impl DataBase { pub fn set_param( &mut self, name: &str, val: &str, scope: Option, ) -> Result<(), ExecError> { if let Ok(Some(nameref)) = self.get_nameref(name) { return self.set_param(&nameref, val, scope); } self.check_on_write(name, &Some(vec![val.to_string()]))?; if name == "BASH_ARGV0" { let n = scope.unwrap_or(self.get_scope_num() - 1); self.position_parameters[n][0] = val.to_string(); } if !self.flags.contains('r') && (self.flags.contains('a') || self.has_flag(name, 'x')) { unsafe{env::set_var(name, "")}; } let scope = self.get_target_scope(name, scope); if self.params[scope].get(name).is_none() { self.set_entry(scope, name, Box::new(SingleData::from("")))?; } let d = self.params[scope].get_mut(name).unwrap(); if let Some(init_d) = d.initialize() { *d = init_d; } d.set_as_single(name, val)?; if env::var(name).is_ok() || self.flags.contains('a') { let v = d.get_as_single()?; unsafe{env::set_var(name, &v)}; } Ok(()) } pub fn set_nameref( &mut self, name: &str, val: &str, scope: Option, ) -> Result<(), ExecError> { self.check_on_write(name, &Some(vec![val.to_string()]))?; if name == "BASH_ARGV0" { let n = scope.unwrap_or(self.get_scope_num() - 1); self.position_parameters[n][0] = val.to_string(); } let scope = self.get_target_scope(name, scope); if self.params[scope].get(name).is_none() { self.set_entry(scope, name, Box::new(SingleData::from("")))?; } let d = self.params[scope].get_mut(name).unwrap(); if let Some(init_d) = d.initialize() { *d = init_d; } d.set_as_single(name, val)?; Ok(()) } pub fn set_param2( &mut self, name: &str, index: &str, val: &str, scope: Option, ) -> Result<(), ExecError> { if let Ok(Some(nameref)) = self.get_nameref(name) { return self.set_param2(&nameref, index, val, scope); } if index.is_empty() { return self.set_param(name, val, scope); } if self.is_array(name) { if let Ok(n) = index.parse::() { self.set_array_elem(name, val, n, scope, false)?; } } else if self.is_assoc(name) { self.set_assoc_elem(name, index, val, scope)?; } else { match index.parse::() { Ok(n) => self.set_array_elem(name, val, n, scope, false)?, _ => self.set_assoc_elem(name, index, val, scope)?, } } Ok(()) } pub fn set_array_elem(&mut self, name: &str, val: &str, pos: isize, scope: Option, append: bool, ) -> Result<(), ExecError> { self.check_on_write(name, &Some(vec![val.to_string()]))?; let scope = self.get_target_scope(name, scope); let i_flag = self.has_flag(name, 'i'); match append { false => self.set_elem(scope, name, pos, &val.to_string(), i_flag), true => self.append_elem(scope, name, pos, &val.to_string()), } } pub fn set_assoc_elem(&mut self, name: &str, key: &str, val: &str, scope: Option, ) -> Result<(), ExecError> { self.check_on_write(name, &Some(vec![val.to_string()]))?; let scope = self.get_target_scope(name, scope); match self.params[scope].get_mut(name) { Some(v) => { if let Some(init_v) = v.initialize() { *v = init_v; } v.set_as_assoc(name, key, val) } _ => Err(ExecError::Other("TODO".to_string())), } } pub fn set_flag(&mut self, name: &str, flag: char, scope: usize) { if let Ok(Some(nameref)) = self.get_nameref(name) { return self.set_flag(&nameref, flag, scope); } match self.params[scope].get_mut(name) { Some(d) => { d.set_flag(flag); }, None => { let obj = match flag { 'i' => Box::new(IntData::new()) as Box::, _ => Box::new(Uninit::new(&flag.to_string())) as Box::, }; let _ = self.set_entry(scope, name, obj); } } } pub fn set_flag_nameref(&mut self, name: &str, flag: char, scope: usize) { match self.params[scope].get_mut(name) { Some(d) => { d.set_flag(flag); }, None => { let obj = match flag { 'i' => Box::new(IntData::new()) as Box::, _ => Box::new(Uninit::new(&flag.to_string())) as Box::, }; let _ = self.set_entry(scope, name, obj); } } } pub fn set_scope_to_env(&mut self, scope: usize) { for (k, v) in &mut self.params[scope] { unsafe{env::set_var(k, v.get_as_single().unwrap_or("".to_string()))}; } } } ================================================ FILE: src/core/database/database_unsetters.rs ================================================ //SPDXFileCopyrightText: 2025 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use crate::core::DataBase; use crate::core::database::Uninit; use crate::error::exec::ExecError; use std::env; impl DataBase { pub fn unset_flag(&mut self, name: &str, flag: char, scope: usize) { if flag != 'n' { if let Ok(Some(nameref)) = self.get_nameref(name) { return self.unset_flag(&nameref, flag, scope); } } let rf = &mut self.params[scope]; if let Some(d) = rf.get_mut(name) { d.unset_flag(flag); } } pub fn unset_flag_nameref(&mut self, name: &str, flag: char, scope: usize) { let rf = &mut self.params[scope]; if let Some(d) = rf.get_mut(name) { d.unset_flag(flag); } } pub fn unset_nameref(&mut self, name: &str, called_scope: Option) -> Result<(), ExecError> { if let Some(scope) = called_scope { if let Some(d) = self.params[scope].get_mut(name) { if d.has_flag('n') { self.remove_entry(scope, name)?; } } return Ok(()); } let num = self.params.len(); for scope in 0..num { if let Some(d) = self.params[scope].get_mut(name) { if d.has_flag('n') { self.remove_entry(scope, name)?; } } } Ok(()) } pub fn unset_var(&mut self, name: &str, called_scope: Option, localvar_unset: bool) -> Result { if let Ok(Some(nameref)) = self.get_nameref(name) { if nameref != "" { return self.unset_var(&nameref, called_scope, localvar_unset); } return Ok(false); } let mut res = false; if let Some(scope) = called_scope { if scope == 0 { return Ok(false); } res = self.remove_entry(scope, name)?; unsafe{env::set_var(name, "")}; for scope in self.params.iter_mut() { if let Some(d) = scope.get_mut(name) { res = true; if localvar_unset { *d = Box::new( Uninit::new("") ); }else { scope.remove(name); } } } return Ok(res); } let num = self.params.len(); for scope in (0..num).rev() { if self.remove_entry(scope, name)? { return Ok(true); } } Ok(res) } pub fn unset_function(&mut self, name: &str) { self.functions.remove(name); } pub fn unset(&mut self, name: &str, called_scope: Option, localvar_unset: bool) -> Result<(), ExecError> { if self.unset_var(name, called_scope, localvar_unset)? { return Ok(()); } self.unset_function(name); Ok(()) } pub fn unset_array_elem(&mut self, name: &str, key: &str) -> Result<(), ExecError> { if self.is_single(name) && (key == "0" || key == "@" || key == "*") { self.unset_var(name, None, false)?; return Ok(()); } for scope in &mut self.params { if let Some(d) = scope.get_mut(name) { d.remove_elem(key)?; } } Ok(()) } } ================================================ FILE: src/core/database.rs ================================================ //SPDXFileCopyrightText: 2025 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause //The methods of DataBase are distributed in database/database_*.rs files. pub mod data; mod database_appenders; mod database_checkers; mod database_getters; mod database_print; mod database_setters; mod database_unsetters; mod database_initializers; use self::data::array::ArrayData; use self::data::array_int::IntArrayData; use self::data::uninit::Uninit; use self::data::assoc::AssocData; use self::data::assoc_int::IntAssocData; use self::data::array_ondemand::OnDemandArray; use self::data::single::SingleData; use self::data::single_int::IntData; use self::data::Data; use crate::elements::command::function_def::FunctionDefinition; use crate::error::exec::ExecError; use std::collections::HashMap; #[derive(Debug, Default)] pub struct DataBase { pub flags: String, pub params: Vec>>, pub position_parameters: Vec>, pub functions: HashMap, pub exit_status: i32, pub last_arg: String, pub hash_counter: HashMap, } impl DataBase { pub fn new() -> DataBase { let mut data = DataBase { params: vec![HashMap::new()], position_parameters: vec![vec![]], flags: "B".to_string(), ..Default::default() }; data.initialize().unwrap(); data } pub fn get_target_scope(&mut self, name: &str, scope: Option) -> usize { match scope { Some(n) => n, None => self.solve_scope(name), } } fn solve_scope(&mut self, name: &str) -> usize { self.get_scope_pos(name).unwrap_or(0) } pub fn push_local(&mut self) { self.params.push(HashMap::new()); } pub fn pop_local(&mut self) { self.params.pop(); } pub fn init(&mut self, name: &str, scope: usize) { if let Some(d) = self.params[scope].get_mut(name) { d.clear(); } } pub fn int_to_str_type(&mut self, name: &str, scope: usize) -> Result<(), ExecError> { let scope_len = self.params.len(); for ly in scope..scope_len { if let Some(v) = self.params[ly].get_mut(name) { let _ = v.unset_flag('i'); } } if let Some(d) = self.params[scope].get_mut(name) { let new_d = d.get_str_type(); self.params[scope].insert(name.to_string(), new_d); } Ok(()) } } ================================================ FILE: src/core/file_descs.rs ================================================ //SPDXFileCopyrightText: 2025 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause extern crate libc; use libc::dup2; use libc::fcntl; use libc::{F_GETFD, F_DUPFD_CLOEXEC}; use crate::error::exec::ExecError; use nix::unistd::Pid; use std::os::fd::{OwnedFd, FromRawFd, RawFd}; use nix::unistd; use std::os::fd::AsRawFd; use std::fs::File; /* We want to control all opening FDs with this. * However, I have no idea how to handle * files and std{in, out, err}. */ #[derive(Default, Debug)] pub struct FileDescriptors { fds: Vec>, } impl FileDescriptors { pub(super) fn new() -> Self { let mut data = Self::default(); for _ in 0..256 { data.fds.push(None); } data } pub fn dupfd_cloexec(&mut self, from: RawFd, hereafter: RawFd) -> Result { let fd = unsafe{fcntl(from, F_DUPFD_CLOEXEC, hereafter)}; self.fds[fd as usize] = Some(unsafe { OwnedFd::from_raw_fd(fd) }); Ok(fd) } pub fn tcsetpgrp(&mut self, fd: RawFd, pgid: Pid) -> Result<(), ExecError> { if let Some(fd) = self.fds[fd as usize].as_mut() { return Ok(unistd::tcsetpgrp(fd, pgid)?); } Ok(()) } pub fn tcgetpgrp(&mut self, fd: RawFd) -> Result { if let Some(fd) = self.fds[fd as usize].as_mut() { return Ok(unistd::tcgetpgrp(fd)?); } Err(ExecError::Other("cannot get process group".to_string())) } pub fn close(&mut self, fd: RawFd) { if fd < 0 || fd >= 256 { return; } self.fds[fd as usize] = None; let _ = unistd::close(fd); } pub fn pipe(&mut self) -> (RawFd, RawFd) { let (recv, send) = unistd::pipe().expect("Cannot open pipe"); let fd_recv = recv.as_raw_fd(); let fd_send = send.as_raw_fd(); self.fds[fd_recv as usize] = Some(recv); self.fds[fd_send as usize] = Some(send); (fd_recv, fd_send) } pub fn backup(&mut self, from: RawFd) -> RawFd { //if fcntl::fcntl(from, fcntl::F_GETFD).is_err() { if unsafe{fcntl(from, F_GETFD)} == -1 { return from; } self.dupfd_cloexec(from, 10).unwrap() } pub fn replace(&mut self, from: RawFd, to: RawFd) -> Result<(), ExecError> { if from < 0 || to < 0 { return Ok(()); } if unsafe{dup2(from, to)} < 0 { return Err(ExecError::Other("dup2 error".to_string())); } //unistd::dup2(from, to)?; self.close(from); Ok(()) } pub fn share(&mut self, from: RawFd, to: RawFd) -> Result<(), ExecError> { if from < 0 || to < 0 { return Err(ExecError::Other("minus fd number".to_string())); } if unsafe{dup2(from, to)} < 0 { //return Err(ExecError::Other("dup2 error".to_string())); return Err(ExecError:: BadFd(from)); } Ok(()) } pub fn get_file(&mut self, fd: RawFd) -> File { let f = self.fds[fd as usize].as_mut().unwrap().try_clone().unwrap(); self.fds[fd as usize] = None; File::from(f) } } ================================================ FILE: src/core/history.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use crate::ShellCore; use rev_lines::RevLines; use std::fs::File; use std::fs::OpenOptions; use std::io::{BufReader, BufWriter, Write}; impl ShellCore { pub fn fetch_history(&mut self, pos: usize, prev: usize, prev_str: String) -> String { if prev < self.history.len() { self.history[prev] = prev_str; } else { self.rewritten_history .insert(prev + 1 - self.history.len(), prev_str); } if pos < self.history.len() { self.history[pos].clone() } else { self.fetch_history_file(pos + 1 - self.history.len()) } } pub fn fetch_history_file(&mut self, pos: usize) -> String { if let Some(s) = self.rewritten_history.get(&pos) { return s.to_string(); } if pos == 0 { return String::new(); } let mut file_line = pos - 1; if let Ok(n) = self .db .get_param("HISTFILESIZE") .unwrap_or_default() .parse::() { file_line %= n; } if let Ok(hist_file) = File::open(self.db.get_param("HISTFILE").unwrap_or_default()) { let mut rev_lines = RevLines::new(BufReader::new(hist_file)); if let Some(Ok(s)) = rev_lines.nth(file_line) { return s; } } String::new() } pub fn write_history_to_file(&mut self) { if !self.db.flags.contains('i') || self.is_subshell { return; } let filename = self.db.get_param("HISTFILE").unwrap_or_default(); if filename.is_empty() { eprintln!("sush: HISTFILE is not set"); return; } let file = match OpenOptions::new().create(true).append(true).open(&filename) { Ok(f) => f, _ => { eprintln!("sush: invalid history file"); return; } }; let mut f = BufWriter::new(file); for h in self.history.iter().rev() { if h.is_empty() { continue; } let _ = f.write(h.as_bytes()); let _ = f.write(&[0x0A]); } let _ = f.flush(); } } ================================================ FILE: src/core/jobtable.rs ================================================ //SPDX-FileCopyrightText: 2023 Ryuichi Ueda ryuichiueda@gmail.com //SPDX-License-Identifier: BSD-3-Clause use crate::error::exec::ExecError; use crate::ShellCore; use nix::sys::signal; use nix::sys::wait; use nix::sys::wait::{WaitPidFlag, WaitStatus}; use nix::unistd; use nix::unistd::Pid; #[derive(Debug, Default)] pub struct JobEntry { pub id: usize, pub pids: Vec, proc_statuses: Vec, pub display_status: String, pub text: String, change: bool, pub no_control: bool, pub coproc_name: Option, pub coproc_fds: Vec, } fn wait_nonblock(pid: &Pid, status: &mut WaitStatus, coproc: bool) -> Result<(), ExecError> { let waitflags = WaitPidFlag::WNOHANG | WaitPidFlag::WUNTRACED | WaitPidFlag::WCONTINUED; match wait::waitpid(*pid, Some(waitflags)) { Ok(s) => { if s != WaitStatus::StillAlive || !still(status) { *status = s; } }, Err(e) => { if ! coproc { return Err(ExecError::Errno(e)); } *status = WaitStatus::Exited(*pid, 0); }, } Ok(()) } fn wait_block(pid: &Pid, status: &mut WaitStatus) -> Result { *status = wait::waitpid(*pid, Some(WaitPidFlag::WUNTRACED))?; let exit_status = match status { WaitStatus::Exited(_, es) => *es, WaitStatus::Stopped(_, _) => 148, WaitStatus::Signaled(_, sig, _) => *sig as i32 + 128, _ => 1, }; Ok(exit_status) } fn still(status: &WaitStatus) -> bool { matches!( status, WaitStatus::StillAlive | WaitStatus::Stopped(_, _) | WaitStatus::Continued(_) ) } impl JobEntry { pub fn new( pids: Vec>, statuses: &[WaitStatus], text: &str, status: &str, id: usize, ) -> JobEntry { JobEntry { id, pids: pids.into_iter().flatten().collect(), proc_statuses: statuses.to_vec(), display_status: status.to_string(), text: text.to_string(), ..Default::default() } } pub fn update_status(&mut self, wait: bool, check_done: bool) -> Result { let mut exit_status = 0; let before = self.proc_statuses[0]; for (status, pid) in self.proc_statuses.iter_mut().zip(&self.pids) { if still(status) { match wait { true => exit_status = wait_block(pid, status)?, false => { wait_nonblock(pid, status, self.coproc_name.is_some())?; } } } } self.change |= before != self.proc_statuses[0]; /* check stopped processes */ let mut stopped = false; for s in &self.proc_statuses { match s { WaitStatus::Stopped(_, _) => { stopped = true; break; } WaitStatus::Exited(_, es) => { if check_done { exit_status = *es; break; } } _ => {} } } if stopped { self.display_status = "Stopped".to_string(); return Ok(148); } if !stopped && self.display_status == "Stopped" || self.change { self.change_display_status(self.proc_statuses[0]); } Ok(exit_status) } pub fn print_p(&self) { println!("{}", self.pids[0]); } pub fn print( &self, priority: &[usize], l_opt: bool, r_opt: bool, s_opt: bool, add_amp: bool, ) -> bool { if r_opt && self.display_status != "Running" || s_opt && self.display_status != "Stopped" { return false; } let symbol = if priority[0] == self.id { "+" } else if priority.len() > 1 && priority[1] == self.id { "-" } else { " " }; let pid = match l_opt { true => &self.pids[0].to_string(), false => "", }; let tmp = self.text.clone(); let text = tmp.trim_end(); if self.display_status == "Stopped" { println!( "[{}]{} {} {} {}", self.id, &symbol, &pid, &self.display_status, &text ); } else if add_amp && self.display_status != "Done" { println!( "[{}]{} {} {} {} &", self.id, &symbol, &pid, &self.display_status, &text ); } else { println!( "[{}]{} {} {} {}", self.id, &symbol, &pid, &self.display_status, &text ); } self.display_status == "Done" || self.display_status == "Killed" } fn display_status_on_signal(signal: &signal::Signal, coredump: bool) -> String { let coredump_msg = match coredump { true => " (core dumped)", false => "", }; let msg = match signal { signal::SIGHUP => "Hangup", signal::SIGINT => "Interrupt", signal::SIGQUIT => "Quit", signal::SIGILL => "Illeagal instruction", signal::SIGTRAP => "Trace/breakpoint trap", signal::SIGABRT => "Aborted", signal::SIGBUS => "Bus error", signal::SIGFPE => "Floating point exception", signal::SIGKILL => "Killed", signal::SIGUSR1 => "User defined signal 1", signal::SIGSEGV => "Segmentation fault", signal::SIGUSR2 => "User defined signal 2", signal::SIGPIPE => "Broken pipe", signal::SIGALRM => "Alarm clock", signal::SIGTERM => "Terminated", // signal::SIGSTKFLT => "Stack fault", not in macOS signal::SIGXCPU => "CPU time limit exceeded", signal::SIGXFSZ => "File size limit exceeded", signal::SIGVTALRM => "Virtual timer expired", signal::SIGPROF => "Profiling timer expired", // signal::SIGPWR => "Power failure", not in macOS signal::SIGSYS => "Bad system call", _ => "", }; (msg.to_owned() + coredump_msg).to_string() } fn change_display_status(&mut self, after: WaitStatus) { self.display_status = match after { WaitStatus::Exited(_, _) => "Done".to_string(), WaitStatus::Stopped(_, _) => "Stopped".to_string(), WaitStatus::Continued(_) => "Running".to_string(), WaitStatus::Signaled(_, signal, coredump) => { Self::display_status_on_signal(&signal, coredump) } _ => return, } } pub fn send_cont(&mut self) { for pid in &self.pids { let _ = signal::kill(Pid::from_raw(-i32::from(*pid)), signal::SIGCONT); } } pub fn solve_pgid(&self) -> Pid { for pid in &self.pids { if let Ok(pgid) = unistd::getpgid(Some(*pid)) { return pgid; } } Pid::from_raw(0) } } impl ShellCore { fn close_coproc(&mut self, pos: usize) { let name = self.job_table[pos].coproc_name.clone().unwrap(); let _ = self.db.unset(&name, None, false); let _ = self.db.unset(&(name.clone() + "_PID"), None, false); if let Ok(fd0) = self.db.get_elem(&name, "0") { if let Ok(n) = fd0.parse::() { let _ = unsafe{libc::close(n)}; } } if let Ok(fd1) = self.db.get_elem(&name, "1") { if let Ok(n) = fd1.parse::() { let _ = unsafe{libc::close(n)}; } } let _ = self.db.unset(&(name), None, false); } pub fn jobtable_check_status(&mut self) -> Result<(), ExecError> { if self.is_subshell { return Ok(()); } let mut stopped = vec![]; for i in 0..self.job_table.len() { let table = &mut self.job_table[i]; if table.update_status(false, false)? == 148 { stopped.push(i); } if table.coproc_name.is_some() && (table.display_status == "Done" || table.display_status == "Killed") { self.close_coproc(i); } } for i in stopped { let job_id = self.job_table[i].id; self.job_table_priority.retain(|id| *id != job_id); self.job_table_priority.insert(0, job_id); } Ok(()) } pub fn jobtable_print_status_change(&mut self) { if self.is_subshell { return; } for e in self.job_table.iter_mut() { if e.change { e.print(&self.job_table_priority, false, false, false, false); e.change = false; } } self.job_table .retain(|e| still(&e.proc_statuses[0]) || e.display_status == "Stopped"); let ids = self.job_table.iter().map(|j| j.id).collect::>(); self.job_table_priority.retain(|id| ids.contains(id)); } pub fn generate_new_job_id(&self) -> usize { match self.job_table.last() { None => 1, Some(job) => job.id + 1, } } pub fn get_jobentry_pid_by_coproc_name(&mut self, name: &str) -> Option { let ans = self.job_table.iter_mut().find(|e| e.coproc_name.is_some() && e.coproc_name.as_ref().unwrap() == name)?; Some(ans.pids[0]) } pub fn get_stopped_job_commands(&self) -> Vec { self.job_table .iter() .map(|j| j.text.split(' ').next().unwrap().to_string()) .collect() } } ================================================ FILE: src/core/options.rs ================================================ //SPDXFileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDXLicense-Identifier: BSD-3-Clause use crate::error::exec::ExecError; use std::collections::HashMap; #[derive(Debug, Default)] pub struct Options { opts: HashMap, pub implemented: Vec, } impl Options { pub fn new_as_basic_opts() -> Options { let mut options = Options::default(); options.opts.insert("allexport".to_string(), false); options.opts.insert("pipefail".to_string(), false); options.opts.insert("monitor".to_string(), true); options.opts.insert("noclobber".to_string(), false); options.opts.insert("noglob".to_string(), false); options.opts.insert("onecmd".to_string(), false); options.opts.insert("posix".to_string(), false); options.opts.insert("history".to_string(), false); //TODO: still dummy options } pub fn new_as_shopts() -> Options { let mut options = Options::default(); let opt_strs = vec![ "autocd", "cdable_vars", "cdspell", "checkhash", "checkjobs", "checkwinsize", "cmdhist", "compat31", "compat32", "compat40", "compat41", "dirspell", "dotglob", "execfail", "expand_aliases", "extdebug", "extglob", "extquote", "failglob", "force_fignore", "globstar", "globskipdots", "gnu_errfmt", "histappend", "histreedit", "histverify", "hostcomplete", "huponexit", "interactive_comments", "lastpipe", "lithist", "login_shell", "mailwarn", "no_empty_cmd_completion", "nocaseglob", "nocasematch", "nullglob", "promptvars", "restricted_shell", "shift_verbose", "sourcepath", "xpg_echo", "assoc_expand_once", "localvar_inherit", "localvar_unset", ]; for opt in opt_strs { options.opts.insert(opt.to_string(), false); } let true_list = ["extglob", "progcomp", "globskipdots"]; for opt in true_list { options.opts.insert(opt.to_string(), true); } options.implemented = [ "extglob", "progcomp", "nullglob", "dotglob", "globstar", "globskipdots", "nocasematch", "expand_aliases", "xpg_echo", "lastpipe", "execfail", "assoc_expand_once", "localvar_inherit", "localvar_unset", ] .iter() .map(|s| s.to_string()) .collect(); //TODO: nocasematch and xpg_echo are dummy options } pub fn format(opt: &str, onoff: bool) -> String { let onoff_str = match onoff { true => "on", false => "off", }; match opt.len() < 16 { true => format!("{opt:16}{onoff_str}"), false => format!("{opt}\t{onoff_str}"), } } pub fn format2(opt: &str, onoff: bool) -> String { let onoff_str = match onoff { true => "-", false => "+", }; format!("set {onoff_str}o {opt}") } pub fn print_opt(&self, opt: &str, set_format: bool) -> bool { match self.opts.get_key_value(opt) { None => { eprintln!("sush: shopt: {opt}: invalid shell option name"); false } Some(kv) => { match set_format { false => println!("{}", Self::format(kv.0, *kv.1)), true => println!("{}", Self::format2(kv.0, *kv.1)), } true } } } pub fn print_all(&self, positive: bool) { let f = match positive { true => Self::format, false => Self::format2, }; let mut list = self .opts .iter() .map(|opt| f(opt.0, *opt.1)) .collect::>(); list.sort(); list.iter().for_each(|e| println!("{e}")); } pub fn print_if(&self, onoff: bool) { let mut list = self .opts .iter() .filter(|opt| *opt.1 == onoff) .map(|opt| Self::format(opt.0, *opt.1)) .collect::>(); list.sort(); list.iter().for_each(|e| println!("{e}")); } pub fn exist(&self, opt: &str) -> bool { self.opts.contains_key(opt) } pub fn query(&self, opt: &str) -> bool { self.exist(opt) && self.opts[opt] } pub fn set(&mut self, opt: &str, onoff: bool) -> Result<(), ExecError> { if !self.opts.contains_key(opt) { let msg = format!("{opt}: invalid option name"); return Err(ExecError::Other(msg)); } self.opts.insert(opt.to_string(), onoff); Ok(()) } pub fn get_keys(&self) -> Vec { self.opts.clone().into_keys().collect() } } ================================================ FILE: src/core.rs ================================================ //SPDX-FileCopyrightText: 2024 Ryuichi Ueda ryuichiueda@gmail.com //SPDX-FileCopyrightText: 2024 @caro@mi.shellgei.org //SPDX-License-Identifier: BSD-3-Clause pub mod builtins; pub mod completion; pub mod database; pub mod history; pub mod jobtable; pub mod options; mod file_descs; use self::completion::{Completion, CompletionEntry}; use self::database::DataBase; use self::options::Options; use self::file_descs::FileDescriptors; use crate::core::jobtable::JobEntry; use crate::elements::substitution::Substitution; use crate::{error, proc_ctrl, signal}; use nix::sys::signal::Signal; use nix::sys::time::{TimeSpec, TimeVal}; use nix::unistd::Pid; use std::collections::HashMap; use std::os::fd::RawFd; //use std::os::fd::{FromRawFd, OwnedFd}; use std::sync::atomic::AtomicBool; use std::sync::Arc; use std::{env, io, path}; use crate::error::exec::ExecError; use crate::file_check; pub struct MeasuredTime { pub real: TimeSpec, pub user: TimeVal, pub sys: TimeVal, } impl Default for MeasuredTime { fn default() -> Self { Self { real: TimeSpec::new(0, 0), user: TimeVal::new(0, 0), sys: TimeVal::new(0, 0), } } } type BuiltinFn = fn(&mut ShellCore, &[String]) -> i32; type SubstBuiltinFn = fn(&mut ShellCore, &[String], &mut [Substitution]) -> i32; #[derive(Default)] pub struct ShellCore { pub db: DataBase, pub alias_memo: Vec<(String, String)>, pub rewritten_history: HashMap, pub history: Vec, pub builtins: HashMap, pub subst_builtins: HashMap, pub sigint: Arc, pub trapped: Vec<(Arc, String)>, pub traplist: Vec<(i32, String)>, pub is_subshell: bool, pub source_function_level: i32, pub source_files: Vec, pub eval_level: i32, pub loop_level: i32, pub break_counter: i32, pub continue_counter: i32, pub return_flag: bool, pub compat_bash: bool, pub fds: FileDescriptors, pub tty_fd: Option, //pub tty_fd: Option, pub job_table: Vec, pub job_table_priority: Vec, current_dir: Option, // the_current_working_directory pub completion: Completion, pub measured_time: MeasuredTime, pub options: Options, pub shopts: Options, pub suspend_e_option: bool, pub script_name: String, pub exit_script: String, pub exit_script_run: bool, pub valid_assoc_expand_once: bool, //pub process_sub: Vec<(Pid, RawFd)>, pub proc_sub_pid: Vec, pub proc_sub_fd: Vec, } impl ShellCore { pub fn configure(&mut self) -> Result<(), ExecError> { self.init_current_directory(); self.set_initial_parameters(); self.set_builtins(); signal::ignore(Signal::SIGPIPE); signal::ignore(Signal::SIGTSTP); let _ = self.db.set_param("PS4", "+ ", None); if file_check::is_tty(0) && self.script_name == "-" { self.db.flags += "himH"; let _ = self.db.set_param("PS1", "🍣 ", None); let _ = self.db.set_param("PS2", "> ", None); self.tty_fd = Some(self.fds.dupfd_cloexec(0, 255)?); } else { self.db.flags += "h"; } let home = self.db.get_param("HOME").unwrap_or_default().to_string(); let _ = self .db .set_param("HISTFILE", &(home + "/.sush_history"), None); let _ = self.db.set_param("HISTFILESIZE", "2000", None); if let Ok("1") = env::var("SUSH_COMPAT_TEST_MODE").as_deref() { if self.db.flags.contains('i') { eprintln!("THIS IS BASH COMPATIBILITY TEST MODE"); } self.compat_bash = true; }; if self.script_name != "-" { let zero = "0".to_string(); let _ = self.db.set_param2("BASH_LINENO", &zero, &zero, None); let _ = self .db .set_param2("BASH_SOURCE", &zero, &self.script_name, None); } Ok(()) } pub fn new() -> Self { ShellCore { db: DataBase::new(), sigint: Arc::new(AtomicBool::new(false)), options: Options::new_as_basic_opts(), shopts: Options::new_as_shopts(), script_name: "-".to_string(), fds: FileDescriptors::new(), ..Default::default() } } pub fn configure_c_mode(&mut self) -> Result<(), ExecError> { if file_check::is_tty(0) { self.tty_fd = Some(self.fds.dupfd_cloexec(0, 255)?); } self.init_current_directory(); self.set_initial_parameters(); self.set_builtins(); signal::ignore(Signal::SIGPIPE); signal::ignore(Signal::SIGTSTP); if let Ok("1") = env::var("SUSH_COMPAT_TEST_MODE").as_deref() { self.compat_bash = true }; Ok(()) } fn set_initial_parameters(&mut self) { let version = env!("CARGO_PKG_VERSION"); let profile = env!("CARGO_BUILD_PROFILE"); let t_arch = env!("CARGO_CFG_TARGET_ARCH"); let t_vendor = env!("CARGO_CFG_TARGET_VENDOR"); let t_os = env!("CARGO_CFG_TARGET_OS"); let machtype = format!("{t_arch}-{t_vendor}-{t_os}"); let symbol = "rusty_bash"; let t_bash_symbol = "bash_compatible"; let t_bash_ver = env!("COMPAT_TARGET_BASH_VERSION"); let vparts = version.split('.').collect(); let tbvparts = t_bash_ver.split('.').collect(); let sush_versinfo = [vparts, vec![symbol, profile, &machtype]] .concat() .iter() .map(|e| e.to_string()) .collect(); let bash_versinfo = [tbvparts, vec![t_bash_symbol, profile, &machtype]] .concat() .iter() .map(|e| e.to_string()) .collect(); let _ = self.db.set_param( "BASH_VERSION", &format!("{t_bash_ver}({t_bash_symbol})-{profile}"), None, ); let _ = self.db.set_param("MACHTYPE", &machtype, None); let _ = self.db.set_param("HOSTTYPE", t_arch, None); let _ = self.db.set_param("OSTYPE", t_os, None); if let Ok("1") = env::var("SUSH_COMPAT_TEST_MODE").as_deref() { }else{ let _ = self .db .init_array("SUSH_VERSINFO", Some(sush_versinfo), None, false); let _ = self.db.set_param( "SUSH_VERSION", &format!("{version}({symbol})-{profile}"), None, ); self.db.set_flag("SUSH_VERSINFO", 'r', 0); } let _ = self .db .init_array("BASH_VERSINFO", Some(bash_versinfo), None, false); self.db.set_flag("BASH_VERSINFO", 'r', 0); } pub fn flip_exit_status(&mut self) { self.db.exit_status = if self.db.exit_status == 0 { 1 } else { 0 }; } fn set_subshell_parameters(&mut self) -> Result<(), String> { let pid = nix::unistd::getpid(); self.db.init_as_num("BASHPID", &pid.to_string(), Some(0))?; match self.db.get_param("BASH_SUBSHELL").unwrap().parse::() { Ok(num) => self .db .set_param("BASH_SUBSHELL", &(num + 1).to_string(), Some(0))?, Err(_) => self.db.set_param("BASH_SUBSHELL", "0", Some(0))?, } Ok(()) } pub fn initialize_as_subshell(&mut self, pid: Pid, pgid: Pid) { signal::restore(Signal::SIGINT); signal::restore(Signal::SIGTSTP); signal::restore(Signal::SIGPIPE); self.is_subshell = true; proc_ctrl::set_pgid(self, pid, pgid); let _ = self.set_subshell_parameters(); //self.job_table.clear(); self.exit_script.clear(); } pub fn init_current_directory(&mut self) { match env::current_dir() { Ok(path) => self.current_dir = Some(path), Err(err) => { let msg = format!("pwd: error retrieving current directory: {err:?}"); error::print(&msg, self); } } } pub fn get_current_directory(&mut self) -> Option { if self.current_dir.is_none() { self.init_current_directory(); } self.current_dir.clone() } pub fn set_current_directory(&mut self, path: &path::PathBuf) -> Result<(), io::Error> { env::set_current_dir(path)?; self.current_dir = Some(path.clone()); Ok(()) } pub fn get_ps4(&mut self) -> String { let ps4 = self .db .get_param("PS4") .unwrap_or_default() .trim_end() .to_string(); let mut multi_ps4 = ps4.to_string(); for _ in 0..(self.source_files.len() as i32 + self.eval_level) { multi_ps4 += &ps4; } multi_ps4 } pub fn replace_alias(&mut self, word: &mut String) -> bool { let before = word.clone(); match self.replace_alias_core(word) { true => { self.alias_memo.push((before, word.clone())); true } false => false, } } fn replace_alias_core(&mut self, word: &mut String) -> bool { if !self.shopts.query("expand_aliases") && !self.db.flags.contains('i') { return false; } let mut ans = false; let mut prev_head = "".to_string(); let history = [word.clone()]; loop { let head = match word.replace("\n", " ").split(' ').nth(0) { Some(h) => h.to_string(), _ => return ans, }; if prev_head == head { return ans; } //if let Some(value) = self.aliases.get(&head) { if self.db.has_array_value("BASH_ALIASES", &head) { let value = self.db.get_elem("BASH_ALIASES", &head).unwrap_or_default(); *word = word.replacen(&head, &value, 1); if history.contains(word) { return false; } ans = true; } prev_head = head; } } } ================================================ FILE: src/elements/ansi_c_str.rs ================================================ //SPDX-FileCopyrightText: 2025 Ryuichi Ueda //SPDX-License-Identifier: BSD-3-Clause use crate::elements::subword::escaped_char::EscapedChar; use crate::elements::subword::simple::SimpleSubword; use crate::elements::subword::Subword; use crate::error::parse::ParseError; use crate::{Feeder, ShellCore}; #[derive(Clone, Debug)] pub enum AnsiCToken { Normal(String), Oct(String), Hex(String), EmptyHex, Unicode4(String), Unicode8(String), Control(char), OtherEscaped(String), } impl AnsiCToken { pub fn render(&mut self) -> String { match &self { AnsiCToken::EmptyHex => String::new(), AnsiCToken::Normal(s) => s.clone(), AnsiCToken::Oct(s) => { let mut num = u32::from_str_radix(s, 8).unwrap(); if num >= 256 { num -= 256; } if num >= 128 { num += 0xE000; char::from_u32(num).unwrap().to_string() } else { char::from(num as u8).to_string() } } AnsiCToken::Hex(s) => { let hex = match s.len() > 2 { true => s[s.len() - 2..].to_string(), false => s.to_string(), }; let mut num = match u32::from_str_radix(&hex, 16) { Ok(n) => n, _ => return String::new(), }; if num >= 256 { num -= 256; } if num >= 128 { num += 0xE000; char::from_u32(num).unwrap().to_string() } else { char::from(num as u8).to_string() } } AnsiCToken::Unicode4(s) => { let num = u32::from_str_radix(s, 16).unwrap(); match char::from_u32(num) { Some(c) => c.to_string(), _ => "U+".to_owned() + s, } } AnsiCToken::Unicode8(s) => { let num = u64::from_str_radix(s, 16).unwrap(); let mut ans = String::new(); for i in (0..4).rev() { let n = (((num >> (i * 8)) & 0xFF) + 0xE000 + ((i + 1) << 8)) as u32; ans.push(char::from_u32(n).unwrap()); } //unsafe { char::from_u32_unchecked(num as u32) }.to_string() ans } AnsiCToken::Control(c) => { let num = if *c == '@' { 0 } else if *c == '[' { 27 } else if *c == '\\' { 28 } else if *c == ']' { 29 } else if *c == '^' { 30 } else if *c == '_' { 31 } else if *c == '?' { 127 } else if c.is_ascii_digit() { *c as u32 - 32 } else if c.is_ascii_lowercase() { *c as u32 - 96 } else if c.is_ascii_uppercase() { *c as u32 - 64 } else if *c as u32 >= 32 { *c as u32 - 32 } else { *c as u32 }; char::from_u32(num).unwrap().to_string() } AnsiCToken::OtherEscaped(s) => match s.as_ref() { "a" => char::from(7).to_string(), "b" => char::from(8).to_string(), "e" | "E" => char::from(27).to_string(), "f" => char::from(12).to_string(), "n" => "\n".to_string(), "r" => "\r".to_string(), "t" => "\t".to_string(), "v" => char::from(11).to_string(), "\\" => "\\".to_string(), "'" => "'".to_string(), "\"" => "\"".to_string(), _ => ("\\".to_owned() + s).to_string(), }, } } } #[derive(Debug, Clone, Default)] pub struct AnsiCString { pub text: String, pub tokens: Vec, } impl AnsiCString { pub fn eval(&mut self) -> String { let mut ans = String::new(); for t in &mut self.tokens { if let AnsiCToken::EmptyHex = t { break; } ans += &t.render(); } ans } fn eat_simple_subword(feeder: &mut Feeder, ans: &mut Self) -> bool { if let Some(a) = SimpleSubword::parse(feeder) { ans.text += a.get_text(); ans.tokens .push(AnsiCToken::Normal(a.get_text().to_string())); true } else { false } } fn eat_oct(feeder: &mut Feeder, ans: &mut Self, core: &mut ShellCore) -> bool { let mut len = feeder.scanner_ansi_c_oct(core); if len < 2 { return false; } if len > 4 { len = 4; } let token = feeder.consume(len); ans.text += &token.clone(); ans.tokens.push(AnsiCToken::Oct(token[1..].to_string())); true } fn eat_hex_braced(feeder: &mut Feeder, ans: &mut Self, core: &mut ShellCore) -> bool { if !feeder.starts_with("\\x{") { return false; } if feeder.starts_with("\\x{}") { ans.text += &feeder.consume(4); ans.tokens.push(AnsiCToken::EmptyHex); return true; } let mut len = feeder.scanner_ansi_c_hex(core); if len < 2 { return false; } if len < 4 { if feeder.len() > len { len += 1; } else { return false; } } let mut token = feeder.consume(len); ans.text += &token.clone(); token.remove(2); token.retain(|c| c != '}'); let ln = token.len(); if ln == 2 { } else if ln == 3 { ans.tokens .push(AnsiCToken::Hex(token[ln - 1..ln].to_string())); } else { ans.tokens .push(AnsiCToken::Hex(token[ln - 2..ln].to_string())); } true } fn eat_hex(feeder: &mut Feeder, ans: &mut Self, core: &mut ShellCore) -> bool { let mut len = feeder.scanner_ansi_c_hex(core); if len < 3 { return false; } if len > 4 { len = 4; } let token = feeder.consume(len); ans.text += &token.clone(); ans.tokens.push(AnsiCToken::Hex(token[2..].to_string())); true } fn eat_unicode4(feeder: &mut Feeder, ans: &mut Self, core: &mut ShellCore) -> bool { let mut len = feeder.scanner_ansi_unicode4(core); if len < 3 { return false; } if len > 6 { len = 6; } let token = feeder.consume(len); ans.text += &token.clone(); ans.tokens .push(AnsiCToken::Unicode4(token[2..].to_string())); true } fn eat_unicode8(feeder: &mut Feeder, ans: &mut Self, core: &mut ShellCore) -> bool { let mut len = feeder.scanner_ansi_unicode8(core); if len < 3 { return false; } if len > 10 { len = 10; } let token = feeder.consume(len); ans.text += &token.clone(); ans.tokens .push(AnsiCToken::Unicode8(token[2..].to_string())); true } fn eat_escaped_char( feeder: &mut Feeder, ans: &mut Self, core: &mut ShellCore, for_echo: bool, ) -> bool { if let Some(a) = EscapedChar::parse(feeder, core) { let txt = a.get_text().to_string(); ans.text += &txt.clone(); if for_echo && txt == "\\'" { ans.tokens.push(AnsiCToken::Normal(txt)); } else if txt != "\\c" || feeder.is_empty() { ans.tokens .push(AnsiCToken::OtherEscaped(txt[1..].to_string())); } else if let Some(a) = EscapedChar::parse(feeder, core) { let mut text_after = a.get_text().to_string(); ans.text += &text_after.clone(); text_after.remove(0); let ctrl_c = text_after.chars().next().unwrap(); ans.tokens.push(AnsiCToken::Control(ctrl_c)); } else if feeder.starts_with("'") { ans.tokens.push(AnsiCToken::Normal("\\c".to_string())); } else { let ctrl_c = feeder.consume(1).chars().next().unwrap(); ans.text += &ctrl_c.to_string(); ans.tokens.push(AnsiCToken::Control(ctrl_c)); } true } else { false } } pub fn parse( feeder: &mut Feeder, core: &mut ShellCore, for_echo: bool, ) -> Result, ParseError> { let mut ans = Self::default(); loop { if !for_echo && feeder.starts_with("'") { break; } if Self::eat_simple_subword(feeder, &mut ans) || Self::eat_hex_braced(feeder, &mut ans, core) || Self::eat_hex(feeder, &mut ans, core) || Self::eat_oct(feeder, &mut ans, core) || Self::eat_unicode4(feeder, &mut ans, core) || Self::eat_unicode8(feeder, &mut ans, core) || Self::eat_escaped_char(feeder, &mut ans, core, for_echo) { continue; } if feeder.is_empty() { if for_echo { break; } feeder.feed_additional_line(core)?; continue; } let other = feeder.consume(1); ans.text += &other.clone(); ans.tokens.push(AnsiCToken::Normal(other)); } Ok(Some(ans)) } } ================================================ FILE: src/elements/command/arithmetic.rs ================================================ //SPDX-FileCopyrightText: 2022 Ryuichi Ueda ryuichiueda@gmail.com //SPDX-License-Identifier: BSD-3-Clause use super::{Command, Redirect}; use crate::elements::expr::arithmetic::ArithmeticExpr; use crate::error::exec::ExecError; use crate::error::parse::ParseError; use crate::{Feeder, ShellCore}; #[derive(Debug, Clone, Default)] pub struct ArithmeticCommand { pub text: String, expressions: Vec, redirects: Vec, force_fork: bool, lineno: usize, } impl Command for ArithmeticCommand { fn run(&mut self, core: &mut ShellCore, _: bool) -> Result<(), ExecError> { let mut err = None; let exit_status = match self.eval(core) { Ok(n) => { if n == "0" { 1 } else { 0 } } Err(e) => { err = Some(e); 1 } }; core.db.exit_status = exit_status; match err { Some(ExecError::ArithError(s, e)) => { if s.starts_with("$(") { ExecError::ArithError(s.clone(), e.clone()).print(core); Err(ExecError::ArithError(s, e)) } else { let err_with_com = ExecError::ArithError("((: ".to_owned() + s.trim_start(), e); err_with_com.print(core); Err(err_with_com) } } Some(e) => { e.print(core); Err(e.clone()) } _ => Ok(()), } } fn get_text(&self) -> String { self.text.clone() } fn get_redirects(&mut self) -> &mut Vec { &mut self.redirects } fn get_lineno(&mut self) -> usize { self.lineno } fn set_force_fork(&mut self) { self.force_fork = true; } fn boxed_clone(&self) -> Box { Box::new(self.clone()) } fn force_fork(&self) -> bool { self.force_fork } } impl ArithmeticCommand { pub fn eval(&mut self, core: &mut ShellCore) -> Result { let mut ans = String::new(); for a in &mut self.expressions { ans = a.eval(core)?; } Ok(ans) } pub fn parse(feeder: &mut Feeder, core: &mut ShellCore) -> Result, ParseError> { if !feeder.starts_with("((") { return Ok(None); } feeder.set_backup(); let mut ans = Self { lineno: feeder.lineno, text: feeder.consume(2), ..Default::default() }; if let Some(c) = ArithmeticExpr::parse(feeder, core, true, "((")? { if feeder.starts_with("))") { ans.text += &c.text; ans.text += &feeder.consume(2); ans.expressions.push(c); feeder.pop_backup(); return Ok(Some(ans)); } } feeder.rewind(); Ok(None) } } ================================================ FILE: src/elements/command/brace.rs ================================================ //SPDX-FileCopyrightText: 2022 Ryuichi Ueda ryuichiueda@gmail.com //SPDX-License-Identifier: BSD-3-Clause use super::{Command, Redirect}; use crate::elements::command; use crate::error::exec::ExecError; use crate::error::parse::ParseError; use crate::utils::exit; use crate::{Feeder, Script, ShellCore}; #[derive(Debug, Clone, Default)] pub struct BraceCommand { text: String, script: Option